Skip to content

Switch Email SDKs

Keep your existing Resend, SendGrid, Mailjet, Postmark, or Mailgun SDK while moving transactional sending to Tinbox, with a full provider compatibility matrix.

Tinbox compat routes let you move transactional email into your own Cloudflare account without rewriting every send call first.

The point is not that you must keep provider SDKs forever. The point is that you can test-drive Tinbox before you touch your application code. Keep the SDK and dependencies you already ship, swap the API key, and change one host/base URL line. When you are ready, move to Tinbox-native APIs on your own schedule.

The simple version:

keep your current SDK
change the API key to a tbx_... key
change one SDK host/base URL line
send through Tinbox

Your app keeps the request shape it already uses. Tinbox translates that provider shape into the canonical Tinbox send pipeline: sender-scope checks, idempotency, suppressions, MIME building, dispatch, and delivery logs.

Every SDK path on this page is backed by a mirror test in @tinbox/compat-sdk-smoke: the official SDK is pointed at localhost, the mirror records the exact method, path, headers, content type, and body it posts, and the compat route is built from that captured wire shape.

The code snippets are also represented in the smoke package as TypeScript fixtures that import the official distributed SDK types. tsc --noEmit must accept those fixtures for Resend, SendGrid, Mailjet, Postmark, and Mailgun before we treat the guide as current.

Compat routes are for transactional sending. They do not clone provider dashboards, hosted templates, audience tools, contact APIs, or webhook signature systems. Those move separately.

Pick your SDK

Best fit: closest drop-in path. The Resend SDK supports baseUrl and RESEND_BASE_URL.

One-line trial: keep resend, change the key, and point baseUrl at Tinbox.

TINBOX_HOST=https://mail.yourdomain.com
TINBOX_API_KEY=tbx_...
RESEND_BASE_URL=https://mail.yourdomain.com/api/send/compat/resend/v1
import { Resend } from "resend";

const resend = new Resend(process.env.TINBOX_API_KEY!, { baseUrl: ${process.env.TINBOX_HOST}/api/send/compat/resend/v1, });

await resend.emails.send({ from: "Acme <hello@yourdomain.com>", to: "ada@example.com", subject: "Your receipt", html: "<p>Thanks.</p>", });

Detailed guide: Switch from Resend.

Best fit: keep @sendgrid/mail for simple transactional sends. Configure the underlying client base URL and use a Tinbox key.

One-line trial: keep @sendgrid/mail, change the key, and set the client baseUrl to Tinbox.

import sgMail from "@sendgrid/mail";
import sgClient from "@sendgrid/client";

sgMail.setApiKey(process.env.TINBOX_API_KEY!); sgClient.setDefaultRequest( "baseUrl", ${process.env.TINBOX_HOST}/api/send/compat/sendgrid, );

await sgMail.send({ from: "hello@yourdomain.com", to: "ada@example.com", subject: "Your receipt", html: "<p>Thanks.</p>", });

Compat route: POST /api/send/compat/sendgrid/v3/mail/send. Vendor-style alias: POST /v3/mail/send.

Detailed guide: Switch from SendGrid.

Best fit: keep node-mailjet. Mailjet's Messages[] body maps naturally to Tinbox batch sends.

One-line trial: keep node-mailjet, change the secret, and set config.host to Tinbox.

import Mailjet from "node-mailjet";

const mailjet = new Mailjet({ apiKey: "tinbox", apiSecret: process.env.TINBOX_API_KEY!, config: { host: "mail.yourdomain.com/api/send/compat/mailjet", version: "v3.1" }, });

await mailjet.post("send", { version: "v3.1" }).request({ Messages: [{ From: { Email: "hello@yourdomain.com", Name: "Acme" }, To: [{ Email: "ada@example.com" }], Subject: "Your receipt", HTMLPart: "<p>Thanks.</p>", }], });

Compat route: POST /api/send/compat/mailjet/v3.1/send. Vendor-style alias: POST /v3.1/send.

Detailed guide: Switch from Mailjet.

Best fit: keep postmark for sendEmail and sendEmailBatch. Postmark uses a host-only config, so Tinbox exposes root aliases.

One-line trial: keep postmark, change the token, and set requestHost to Tinbox.

import postmark from "postmark";

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

await client.sendEmail({ From: "hello@yourdomain.com", To: "ada@example.com", Subject: "Your receipt", HtmlBody: "<p>Thanks.</p>", });

Canonical compat routes are POST /api/send/compat/postmark/v1/email and POST /api/send/compat/postmark/v1/email/batch. The official SDK uses the root aliases POST /email and POST /email/batch.

Detailed guide: Switch from Postmark.

Best fit: keep mailgun.js for basic messages.create sends. The SDK smoke test verifies multipart/form-data posts to the canonical Tinbox compat prefix.

One-line trial: keep mailgun.js, change the key, and set the client url to Tinbox.

import Mailgun from "mailgun.js";

const mailgun = new Mailgun(FormData); const mg = mailgun.client({ username: "api", key: process.env.TINBOX_API_KEY!, url: ${process.env.TINBOX_HOST}/api/send/compat/mailgun, });

await mg.messages.create("yourdomain.com", { from: "hello@yourdomain.com", to: ["ada@example.com"], subject: "Your receipt", html: "<p>Thanks.</p>", });

Compat route: POST /api/send/compat/mailgun/v3/:domain/messages. Vendor-style alias: POST /v3/:domain/messages.

Detailed guide: Switch from Mailgun.

What stays the same

For simple transactional email, most projects keep the same application-level send call:

  • same from, to, cc, bcc
  • same subject
  • same html and text
  • same base64 attachments
  • same reply-to intent
  • same SDK-side ergonomics

What changes is the delivery plane. The API key becomes a Tinbox sending key, the host points at your Tinbox deployment, and Tinbox stores the send ledger in your database.

Compatibility levels

LevelMeaning
L1Keep the official SDK. Change host/base URL and key only.
L2Keep the official SDK, with route aliases or small constructor config.
L3Keep the request body over fetch; official SDK targeting is imperfect.
L4Use a small app adapter because the provider feature is too different.

Provider routes

Every provider gets a Tinbox-owned compat path under /api/send/compat. Some providers also get vendor-style aliases so official SDKs with host-only config can still work.

The official SDK mirror suite currently validates these package versions:

ProviderSDK packageTested versionSDK path mode
Resendresend6.17.2canonical /api/send/compat/... prefix
SendGrid@sendgrid/mail + @sendgrid/client8.1.6canonical /api/send/compat/... prefix
Mailjetnode-mailjet6.0.11canonical /api/send/compat/... prefix
Postmarkpostmark5.0.0root aliases (/email, /email/batch)
Mailgunmailgun.js13.2.0canonical /api/send/compat/... prefix
ProviderCanonical Tinbox compat routeVendor-style alias
ResendPOST /api/send/compat/resend/v1/emailsPOST /api/send/emails
Resend batchPOST /api/send/compat/resend/v1/emails/batchPOST /api/send/emails/batch
SendGridPOST /api/send/compat/sendgrid/v3/mail/sendPOST /v3/mail/send
MailjetPOST /api/send/compat/mailjet/v3.1/sendPOST /v3.1/send
PostmarkPOST /api/send/compat/postmark/v1/emailPOST /email
Postmark batchPOST /api/send/compat/postmark/v1/email/batchPOST /email/batch
MailgunPOST /api/send/compat/mailgun/v3/:domain/messagesPOST /v3/:domain/messages
Mailgun raw MIMEPOST /api/send/compat/mailgun/v3/:domain/messages.mimePOST /v3/:domain/messages.mime

Full parity matrix

FeatureResendSendGridMailjetPostmarkMailgun
Compat levelL1L1/L2L1/L2L2L2/L3
Wire API versionv1-style /emailsv3v3.1v1-style /emailv3
Keep official SDKYesYes, for simple sendsYesYes, with root aliasesPartial
Host/base URL overridebaseUrl, RESEND_BASE_URLsetDefaultRequest("baseUrl")config.hostrequestHosturl
Tinbox route/api/send/compat/resend/v1/emails/api/send/compat/sendgrid/v3/mail/send/api/send/compat/mailjet/v3.1/send/api/send/compat/postmark/v1/email/api/send/compat/mailgun/v3/:domain/messages
Auth acceptedBearer tbx_...Bearer tbx_...Basic password or bearerX-Postmark-Server-Token or bearerBasic password or bearer
PayloadJSONJSONJSONJSONmultipart/form-data
Single sendSupportedSupportedSupportedSupportedSupported
Batch sendSupported if SDK route matchesPartial via personalizationsSupported via Messages[]SupportedPartial/client loop
to / cc / bccSupportedSupportedSupportedSupported with comma parsingSupported
Reply-ToSupportedSupportedSupportedSupportedSupported via h:Reply-To
HTML + textSupportedSupported via content partsSupportedSupportedSupported
Custom headersPartial, X-* onlyPartial, X-* onlyPartial, X-* onlyPartial, X-* onlyPartial, h:X-* only
IdempotencySupported via Idempotency-KeyPartial via explicit Tinbox custom arg/headerPartial via CustomID or headerPartial via metadata or headerPartial via variable/header
Base64 attachmentsSupportedSupportedSupportedSupportedSupported after form-file conversion
Path/URL attachmentsUnsupported unless SDK pre-convertsUnsupported unless SDK pre-convertsUnsupportedUnsupportedNot applicable
Inline/CID attachmentsNot yetNot yetNot yetNot yetNot yet
Hosted templatesNot a compat featureNot a compat featureNot a compat featureNot a compat featureNot a compat feature
Scheduled sendsNot a compat featureNot a compat featureNot a compat featureNot a compat featureNot a compat feature
Tags / metadataPartial/ignoredPartial, later compat metadataPartial, CustomID onlyPartial, later metadataPartial, later variables
Suppression APIsUse Tinbox nativeUse Tinbox nativeUse Tinbox nativeUse Tinbox nativeUse Tinbox native
Webhooks/eventsUse Tinbox nativeUse Tinbox nativeUse Tinbox nativeUse Tinbox nativeUse Tinbox native
Success responseResend SDK wrapper over Tinbox body202 + message headerMailjet Messages[]Postmark message responseMailgun queued response

Compatibility warnings

Dangerous gaps fail fast with 422: hosted templates, scheduled sends, and raw MIME routes that Tinbox does not support yet. Lower-risk degraded fields are accepted but return an X-Tinbox-Compat-Warnings header. The response body stays provider-shaped so official SDKs keep parsing it.

Examples of warning-only fields:

ProviderWarning examples
Resendtags
SendGridextra personalizations[], asm, mail_settings, tracking_settings, categories
MailjetInlinedAttachments, EventPayload, MonitoringCategory
PostmarkMessageStream, Tag, Metadata, TrackOpens, TrackLinks
Mailgunnon-X-* h:* headers, tracking/campaign options, recipient variables

SDK gaps at a glance

These are the gaps that should be visible before a user changes production traffic. Compat routes should either reject these fields clearly or document that the value is ignored.

SDKMain gapsWhat to do instead
ResendscheduledAt, hosted templates, audiences/broadcasts/topics, Resend webhook verification, attachment paths when not pre-convertedSchedule in your app, render templates before send, migrate events to Tinbox logs/webhooks, pass base64 attachments
SendGridtemplate_id, dynamic_template_data, send_at, ASM unsubscribe groups, tracking settings, advanced substitutions, non-X-* headersRender templates before send, use Tinbox audiences/suppressions, use Tinbox events, keep custom mail headers under X-*
MailjetTemplateID, Variables, InlinedAttachments, sandbox mode, Mailjet event payload semanticsRender templates before send, avoid inline/CID attachments for now, use Tinbox test flows and event logs
Postmarktemplate endpoints, MessageStream, Tag/Metadata analytics parity, content IDs, non-X-* headersUse /email with rendered content, use Tinbox logs for delivery visibility, keep custom mail headers under X-*
Mailgunraw MIME passthrough, inline files, recipient variables, tags/campaign analytics, stored templatesUse basic /messages first, render variables/templates in your app, use Tinbox events and logs

What does not move through compat routes

Compat routes are deliberately narrow. These should be migrated to Tinbox-native features or app code:

  • provider-hosted templates
  • scheduled send APIs
  • broadcasts, audiences, contacts, and marketing list APIs
  • provider webhook signature verification
  • provider analytics dashboards
  • domain and sender management APIs
  • raw MIME passthrough
  1. Deploy Tinbox and verify your sending domain.
  2. Create a Tinbox sending API key scoped to your app's from address or domain.
  3. Pick your current SDK above and change only the key and one host/base URL line.
  4. Send a smoke test from the same from address production uses.
  5. Check the Tinbox send ledger and the received email's SPF/DKIM/DMARC headers.
  6. Move advanced provider features one by one after simple sends are stable.

Detailed guides