Skip to content

Switch from Mailgun

Replace Mailgun's basic messages.create transactional send path with Tinbox while keeping mailgun.js where it fits.

Tinbox can replace the API side of a Mailgun transactional integration when your app mainly uses mailgun.js messages.create(domain, data) with rendered content.

Mailgun has a broad API surface. Treat this as a drop-in path for basic transactional /messages sends, not for templates, recipient variables, stored messages, routes, inbound parsing, or raw MIME passthrough.

For that core path, the trial is intentionally small: keep mailgun.js, change the key, and point one url option at Tinbox.

What changes

Mailgun project settingTinbox replacement
Mailgun private API keyTINBOX_API_KEY=tbx_...
https://api.mailgun.nethttps://<your-tinbox-host>/api/send/compat/mailgun
Basic auth passwordThe Tinbox key
Mailgun logs/eventsTinbox send ledger and delivery events
Mailgun domain verificationTinbox sending domain verification

One-line test drive

Keep your existing mg.messages.create(...) calls. Change the client URL:

url: `${process.env.TINBOX_HOST}/api/send/compat/mailgun`

Then use a Tinbox tbx_... key as the Mailgun client key. Tinbox receives the Mailgun-shaped multipart request.

Inventory your Mailgun usage

rg "from ['\"]mailgun\\.js['\"]|messages\\.create\\(|messages\\.send|messages\\.resend"
rg "template|'t:variables'|'recipient-variables'|'o:deliverytime'|messages\\.mime|inline"
UsageMigration path
messages.create(domain, { from, to, subject, html, text })Keep mailgun.js
cc, bcc, h:Reply-To, h:X-*, basic attachmentsSupported
template, t:variables, recipient variablesRender before calling Tinbox
tracking, tags, campaign optionsAccepted with warnings, not analytics parity
raw MIME or stored-message APIsNot drop-in yet

Configure the SDK

TINBOX_HOST=https://mail.yourdomain.com
TINBOX_API_KEY=tbx_...
import FormData from "form-data";
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"],
  cc: ["billing@example.com"],
  bcc: ["audit@example.com"],
  subject: "Your receipt",
  html: "&#x3C;p>Thanks.&#x3C;/p>",
  text: "Thanks.",
  "h:Reply-To": "support@yourdomain.com",
  "h:X-Receipt-ID": "receipt-4821",
  attachment: {
    data: Buffer.from("receipt"),
    filename: "receipt.txt",
    contentType: "text/plain",
  },
  "v:tinbox_idempotency_key": "receipt-4821",
});

The SDK posts multipart form data to:

POST /api/send/compat/mailgun/v3/:domain/messages

Tinbox also exposes POST /v3/:domain/messages for clients that can only change host.

Field mapping

Mailgun fieldTinbox supportNotes
from, to, cc, bccYesString or array recipients.
subjectYesPer message.
html, textYesAt least one body part is required.
h:Reply-ToYesMaps to Tinbox reply-to.
h:X-*PartialCustom X-* headers only.
attachmentYesConverted from multipart form files.
v:tinbox_idempotency_keyPartialUseful for correlation/idempotency metadata.
template, t:variables, recipient-variablesNoRender before send.
o:deliverytime, STO/TZO optionsNoSchedule outside the compat route.
o:tracking*, o:tag, campaign optionsWarningAccepted but not treated as Mailgun analytics controls.
/messages.mimeNoRaw MIME passthrough is rejected until supported.

Warnings and failures

Raw MIME, template, and schedule expectations fail with 422 when Tinbox cannot honor them safely. Tracking, campaign, and non-X-* header fields return X-Tinbox-Compat-Warnings while preserving a Mailgun-like queued response.

Tinbox-native path

import { Tinbox } from "tinbox/sdk";

const tinbox = new Tinbox({
  host: 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 Mailgun from identity.
  3. Point mailgun.js url at /api/send/compat/mailgun.
  4. Smoke test one message with HTML/text, reply-to, and an attachment if used.
  5. Replace templates, recipient variables, schedules, raw MIME, and Mailgun analytics dependencies before moving those flows.

Source note

This guide is validated against mailgun.js v13.2.0 with form-data v4.0.6. The SDK mirror test records the exact method, path, basic-auth style, multipart content type, and body fields emitted by the official package.