Skip to content

Switch from Postmark

Replace Postmark's transactional Email API with Tinbox while keeping the postmark ServerClient for sendEmail and sendEmailBatch.

Tinbox can replace the API side of a Postmark transactional integration when your app mainly uses postmark.ServerClient#sendEmail or sendEmailBatch.

Postmark's Node SDK accepts a custom requestHost, but not a path prefix, so Tinbox exposes root aliases for the official SDK: /email and /email/batch. That means the trial is still tiny: keep postmark, change the token, and point one host option at Tinbox.

What changes

Postmark project settingTinbox replacement
Postmark server tokenTINBOX_API_KEY=tbx_...
api.postmarkapp.com<your-tinbox-host>
X-Postmark-Server-TokenThe official SDK keeps this header, with the Tinbox key as its value
Postmark activity feedTinbox send ledger and delivery events
Postmark sender signature/domainTinbox sending domain verification

One-line test drive

Keep your existing client.sendEmail(...) and client.sendEmailBatch(...) calls. Change the ServerClient host option:

const client = new postmark.ServerClient(process.env.TINBOX_API_KEY!, {
  useHttps: true,
  requestHost: "mail.yourdomain.com",
});

Tinbox handles the SDK's root /email and /email/batch requests through Postmark-compatible aliases.

Inventory your Postmark usage

rg "from ['\"]postmark['\"]|new postmark\\.ServerClient|sendEmail\\(|sendEmailBatch\\("
rg "sendEmailWithTemplate|MessageStream|TrackOpens|TrackLinks|Metadata|Tag"
UsageMigration path
sendEmail with rendered HtmlBody or TextBodyKeep the Postmark SDK
sendEmailBatchKeep the Postmark SDK; Tinbox maps each message
From, To, Cc, Bcc, ReplyTo, SubjectSupported
Headers and base64 AttachmentsSupported with limits
Template APIsRender before calling Tinbox
MessageStream, Tag, Metadata, tracking flagsAccepted as warnings or metadata, not analytics parity

Configure the SDK

TINBOX_HOST=mail.yourdomain.com
TINBOX_API_KEY=tbx_...
import postmark from "postmark";

const client = new postmark.ServerClient(process.env.TINBOX_API_KEY!, {
  useHttps: true,
  requestHost: process.env.TINBOX_HOST!,
});

await client.sendEmail({
  From: "hello@yourdomain.com",
  To: "ada@example.com",
  Cc: "billing@example.com",
  Bcc: "audit@example.com",
  ReplyTo: "support@yourdomain.com",
  Subject: "Your receipt",
  HtmlBody: "&#x3C;p>Thanks.&#x3C;/p>",
  TextBody: "Thanks.",
  Headers: [{ Name: "X-Receipt-ID", Value: "receipt-4821" }],
  Attachments: [
    {
      Name: "receipt.txt",
      Content: Buffer.from("receipt").toString("base64"),
      ContentType: "text/plain",
      ContentID: "",
    },
  ],
  Metadata: { tinbox_idempotency_key: "receipt-4821" },
});

The official SDK posts to:

POST /email
POST /email/batch

Tinbox also keeps canonical provider routes for direct callers:

POST /api/send/compat/postmark/v1/email
POST /api/send/compat/postmark/v1/email/batch

Batch sends

await client.sendEmailBatch([
  {
    From: "hello@yourdomain.com",
    To: "ada@example.com",
    Subject: "Your receipt",
    TextBody: "Thanks.",
  },
  {
    From: "hello@yourdomain.com",
    To: "grace@example.com",
    Subject: "Your receipt",
    TextBody: "Thanks.",
  },
]);

Field mapping

Postmark fieldTinbox supportNotes
From, To, Cc, BccYesComma-separated recipient strings are parsed.
ReplyToYesMaps to Tinbox reply-to.
SubjectYesPer message.
HtmlBody, TextBodyYesAt least one body part is required.
HeadersPartialCustom X-* headers only.
AttachmentsYesBase64 Content is required.
MetadataPartialUseful for correlation; not Postmark analytics parity.
MessageStream, Tag, TrackOpens, TrackLinksWarningAccepted but degraded to Tinbox logs/events.
Template APIsNoRender before send.
Inline CID attachmentsNoNot drop-in yet.

Warnings and failures

Template sends fail with 422 when Tinbox cannot safely honor them. Degraded analytics fields return X-Tinbox-Compat-Warnings while keeping a Postmark-like response shape.

Tinbox-native path

import { Tinbox } from "tinbox/sdk";

const tinbox = new Tinbox({
  host: `https://${process.env.TINBOX_HOST}`,
  apiKey: process.env.TINBOX_API_KEY!,
});

await tinbox.emails.send({
  from: "hello@yourdomain.com",
  to: "ada@example.com",
  subject: "Your receipt",
  html: "&#x3C;p>Thanks.&#x3C;/p>",
  idempotencyKey: "receipt-4821",
});

Cutover checklist

  1. Verify the Tinbox sending domain.
  2. Create a Tinbox key scoped to the Postmark From address.
  3. Configure ServerClient with requestHost.
  4. Smoke test both sendEmail and sendEmailBatch.
  5. Replace Postmark templates, streams, tags, and tracking dashboards with app-side rendering and Tinbox delivery visibility.

Source note

This guide is validated against postmark v5.0.0. The SDK mirror test records the exact method, root alias path, X-Postmark-Server-Token auth style, content type, and JSON body emitted by the official package.