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.
X-Postmark-Server-Token: afn_live_YOUR_WEMAIL_KEYSupported 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
| Parameter | Type | Required | Description |
|---|---|---|---|
From | string | Required | Sender — must use a domain verified in your wemail workspace. Display names ("Name <a@b.com>") supported. |
To / Cc / Bcc | string | Required | Comma-separated recipient lists (To required). |
Subject | string | Optional | Required unless TemplateId is provided. |
HtmlBody / TextBody | string | Optional | Body parts. At least one of HtmlBody, TextBody, TemplateId is required. |
TemplateId / TemplateModel | string / object | Optional | A wemail template id (tpl_…) + its variables. Numeric Postmark template ids are not resolvable — re-point to the migrated template's wemail id. |
Tag | string | Optional | Single tag, echoed on events and webhooks (Postmark allows one tag per message). |
Metadata | object | Optional | Custom metadata, echoed on webhooks. |
Headers | array | Optional | [{ Name, Value }] custom SMTP headers. |
TrackOpens / TrackLinks | boolean / string | Optional | Tracking controls, mapped to wemail tracking (None, HtmlAndText, HtmlOnly, TextOnly). |
ReplyTo | string | Optional | Reply-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
| Parameter | Type | Required | Description |
|---|---|---|---|
TemplateId | behavior | Optional | Numeric Postmark template ids and aliases are not resolvable; use the wemail tpl_… id of the migrated template. |
MessageStreams | behavior | Optional | The MessageStream field is accepted and ignored — wemail routes all bridge traffic through the transactional pipeline. |
Attachments | unsupported | Optional | Not yet supported through the bridge; use the native wemail /v3/send for attachments. |
Inbound | unsupported | Optional | Postmark 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 -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>"
}'// 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>',
});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, ...}