Postmark Bridge

Drop-in replacement for the Postmark email API — keep the X-Postmark-Server-Token header and your existing request structure, swap in your wemail API key.

The Postmark bridge lets you migrate from Postmark to wemail without changing your integration code. Point your base URL to https://api.wemail.io/bridge/postmark, put your wemail API key in the same X-Postmark-Server-Token header, and keep the same request bodies and response shapes — single sends, batches, outbound message search, and bounce listings all work the same way.

Authentication

Exactly like Postmark, the bridge authenticates with the X-Postmark-Server-Token header. Use your wemail API key (afn_live_… or afn_test_…) where your Postmark server token used to be. Test-environment keys route through the sandbox: messages are accepted, simulated, and never delivered.

bashAuthentication header
X-Postmark-Server-Token: afn_live_YOUR_WEMAIL_KEY

Supported Endpoints

POST /bridge/postmark/email

Send a single email — mirrors Postmark's Send a single email endpoint.

POST /bridge/postmark/email/batch

Send up to 500 emails in one call — mirrors Postmark's batch endpoint with per-message results.

GET /bridge/postmark/messages/outbound

Search outbound messages — mirrors Postmark's outbound message search.

GET /bridge/postmark/bounces

List bounces — mirrors Postmark's bounce listing.

Field-Level Compatibility

POST /email — Request Fields

Send request fields
ParameterTypeRequiredDescription
FromstringRequiredSender — must use a domain verified in your wemail workspace. Display names ("Name <a@b.com>") supported.
To / Cc / BccstringRequiredComma-separated recipient lists (To required).
SubjectstringOptionalRequired unless TemplateId is provided.
HtmlBody / TextBodystringOptionalBody parts. At least one of HtmlBody, TextBody, TemplateId is required.
TemplateId / TemplateModelstring / objectOptionalA wemail template id (tpl_…) + its variables. Numeric Postmark template ids are not resolvable — re-point to the migrated template's wemail id.
TagstringOptionalSingle tag, echoed on events and webhooks (Postmark allows one tag per message).
MetadataobjectOptionalCustom metadata, echoed on webhooks.
HeadersarrayOptional[{ Name, Value }] custom SMTP headers.
TrackOpens / TrackLinksboolean / stringOptionalTracking controls, mapped to wemail tracking (None, HtmlAndText, HtmlOnly, TextOnly).
ReplyTostringOptionalReply-To address.

Response

A successful send returns 200 with { To, SubmittedAt, MessageID, ErrorCode: 0, Message: "OK" }. Validation failures return 422 with { ErrorCode, Message } using Postmark's error-code convention (300 = invalid request, 10 = auth). The batch endpoint returns an array with one result object per message — failures are per-item, exactly like Postmark.

GET /messages/outbound & GET /bounces

Both return Postmark envelopes: { TotalCount, Messages: [...] } and { TotalCount, Bounces: [...] }, paginated with count/offset. Statuses and bounce types are mapped from the wemail pipeline to Postmark vocabulary.

Webhooks

When the Postmark bridge is active, webhook deliveries are formatted exactly like Postmark webhooks: a single PascalCase object per event with RecordType (Delivery, Bounce, SpamComplaint, Open, Click), MessageID, Recipient, Tag, Metadata, and record-specific fields (Type/TypeCode/Description on bounces, Client/OS/Geo on opens and clicks). Like Postmark, payloads are not signed — protect your endpoint with HTTP auth in the URL or network rules.

Known Deviations from Postmark API

Deviations
ParameterTypeRequiredDescription
TemplateIdbehaviorOptionalNumeric Postmark template ids and aliases are not resolvable; use the wemail tpl_… id of the migrated template.
MessageStreamsbehaviorOptionalThe MessageStream field is accepted and ignored — wemail routes all bridge traffic through the transactional pipeline.
AttachmentsunsupportedOptionalNot yet supported through the bridge; use the native wemail /v3/send for attachments.
InboundunsupportedOptionalPostmark inbound processing is not part of the bridge; wemail's SMTP inbound is configured separately.

Migrating for good? Follow the Postmark migration guide — templates and suppressions auto-import, and Postmark stays warm for 30 days.

Code examples

cURL
curl -X POST https://api.wemail.io/bridge/postmark/email \
  -H "X-Postmark-Server-Token: afn_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "From": "sender@yourdomain.com",
    "To": "user@example.com",
    "Subject": "Hello from the Postmark bridge",
    "HtmlBody": "<p>It works — zero code changes.</p>"
  }'
Node.js
// Postmark SDK — point the client at the wemail bridge
const postmark = require('postmark');

const client = new postmark.ServerClient('afn_live_YOUR_KEY', {
  requestHost: 'api.wemail.io/bridge/postmark',
});

await client.sendEmail({
  From: 'sender@yourdomain.com',
  To: 'user@example.com',
  Subject: 'Hello from the Postmark bridge',
  HtmlBody: '<p>It works — zero code changes.</p>',
});
Python
import requests

resp = requests.post(
    "https://api.wemail.io/bridge/postmark/email",
    headers={"X-Postmark-Server-Token": "afn_live_YOUR_KEY"},
    json={
        "From": "sender@yourdomain.com",
        "To": "user@example.com",
        "Subject": "Hello from the Postmark bridge",
        "HtmlBody": "<p>It works — zero code changes.</p>",
    },
)
print(resp.status_code, resp.json())  # 200 {'MessageID': 'msg_…', 'ErrorCode': 0, ...}