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 setting | Tinbox replacement |
|---|---|
| Mailgun private API key | TINBOX_API_KEY=tbx_... |
https://api.mailgun.net | https://<your-tinbox-host>/api/send/compat/mailgun |
| Basic auth password | The Tinbox key |
| Mailgun logs/events | Tinbox send ledger and delivery events |
| Mailgun domain verification | Tinbox 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"
| Usage | Migration path |
|---|---|
messages.create(domain, { from, to, subject, html, text }) | Keep mailgun.js |
cc, bcc, h:Reply-To, h:X-*, basic attachments | Supported |
template, t:variables, recipient variables | Render before calling Tinbox |
| tracking, tags, campaign options | Accepted with warnings, not analytics parity |
| raw MIME or stored-message APIs | Not 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: "<p>Thanks.</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 field | Tinbox support | Notes |
|---|---|---|
from, to, cc, bcc | Yes | String or array recipients. |
subject | Yes | Per message. |
html, text | Yes | At least one body part is required. |
h:Reply-To | Yes | Maps to Tinbox reply-to. |
h:X-* | Partial | Custom X-* headers only. |
attachment | Yes | Converted from multipart form files. |
v:tinbox_idempotency_key | Partial | Useful for correlation/idempotency metadata. |
template, t:variables, recipient-variables | No | Render before send. |
o:deliverytime, STO/TZO options | No | Schedule outside the compat route. |
o:tracking*, o:tag, campaign options | Warning | Accepted but not treated as Mailgun analytics controls. |
/messages.mime | No | Raw 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: "<p>Thanks.</p>",
idempotencyKey: "receipt-4821",
});
Cutover checklist
- Verify the Tinbox sending domain.
- Create a Tinbox key scoped to the Mailgun
fromidentity. - Point
mailgun.jsurlat/api/send/compat/mailgun. - Smoke test one message with HTML/text, reply-to, and an attachment if used.
- 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.