Switch from SendGrid
Replace SendGrid's transactional Mail Send API with Tinbox while keeping @sendgrid/mail for simple sends.
Tinbox can replace the API side of a SendGrid transactional integration when
your app mainly uses @sendgrid/mail for receipts, password resets, alerts, and
product notifications.
The smallest migration is to keep @sendgrid/mail, replace the API key with a
Tinbox tbx_... sending key, and change one client host line. That lets you
test-drive Tinbox without removing SendGrid packages or rewriting send calls.
What changes
| SendGrid project setting | Tinbox replacement |
|---|---|
SENDGRID_API_KEY=SG... | TINBOX_API_KEY=tbx_..., or keep the old env name temporarily with a Tinbox key |
https://api.sendgrid.com | https://<your-tinbox-host>/api/send/compat/sendgrid |
sgMail.setApiKey(process.env.SENDGRID_API_KEY) | sgMail.setApiKey(process.env.TINBOX_API_KEY) |
| SendGrid Email API logs | Tinbox send ledger and delivery events |
| SendGrid verified sender/domain | Tinbox sending domain verification in your Cloudflare account |
One-line test drive
Keep your existing sgMail.send(...) calls. Add or change the underlying client
base URL:
sgClient.setDefaultRequest("baseUrl", `${process.env.TINBOX_HOST}/api/send/compat/sendgrid`);
Then put a Tinbox tbx_... key in the API key env var. Your application still
calls sgMail.send(...); Tinbox receives the SendGrid-shaped request.
Inventory your SendGrid usage
rg "from ['\"]@sendgrid/(mail|client)['\"]|sgMail\\.send|setApiKey\\("
rg "SENDGRID_API_KEY|api\\.sendgrid\\.com|template_id|send_at|asm|tracking_settings"
| Usage | Migration path |
|---|---|
sgMail.send with from, to, subject, html, text, cc, bcc, replyTo, headers, base64 attachments | Keep the SendGrid SDK |
personalizations[] for one rendered message shape | Supported for the first personalization; extras warn |
template_id, dynamic_template_data | Render the template in your app, then send html/text |
send_at | Schedule in your app or queue before calling Tinbox |
| ASM, categories, tracking settings, substitution systems | Move to Tinbox suppressions/events or app code |
Configure the SDK
TINBOX_HOST=https://mail.yourdomain.com
TINBOX_API_KEY=tbx_...
import sgClient from "@sendgrid/client";
import sgMail from "@sendgrid/mail";
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",
cc: "billing@example.com",
bcc: "audit@example.com",
replyTo: "support@yourdomain.com",
subject: "Your receipt",
html: "<p>Thanks.</p>",
text: "Thanks.",
headers: { "X-Receipt-ID": "receipt-4821" },
attachments: [
{
filename: "receipt.txt",
content: Buffer.from("receipt").toString("base64"),
type: "text/plain",
disposition: "attachment",
},
],
customArgs: {
tinbox_idempotency_key: "receipt-4821",
},
});
The SDK posts to /v3/mail/send under the configured base URL, so the request
lands at:
POST /api/send/compat/sendgrid/v3/mail/send
Tinbox also exposes the vendor-style alias POST /v3/mail/send for clients that
can only change host.
Field mapping
| SendGrid field | Tinbox support | Notes |
|---|---|---|
from | Yes | Enforced against the Tinbox key's sender scope. |
personalizations[0].to, cc, bcc | Yes | Extra personalizations are accepted with a warning. |
top-level to, cc, bcc via @sendgrid/mail | Yes | The SDK normalizes these before sending. |
subject | Yes | Top-level or first personalization. |
content, html, text | Yes | The SDK emits content[]; Tinbox accepts HTML and plain text parts. |
reply_to / replyTo | Yes | SDK normalizes camelCase. |
headers | Partial | Custom X-* headers only. |
attachments[].content | Yes | Base64 content. |
custom_args.tinbox_idempotency_key | Partial | Used as an app-level idempotency hint where configured. |
template_id, dynamic_template_data | No | Render before calling Tinbox. |
send_at | No | Schedule outside the compat route. |
asm, categories, mail_settings, tracking_settings | Warning | Accepted but not treated as SendGrid analytics controls. |
Warnings and failures
Template and schedule expectations fail with 422 when Tinbox cannot safely
honor them. Lower-risk fields such as categories, tracking settings, and extra
personalizations return X-Tinbox-Compat-Warnings while preserving a
SendGrid-shaped success response.
Tinbox-native path
Use the Tinbox SDK when you want native status reads, suppressions, and response types:
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 sending key scoped to the production
fromaddress. - Configure
@sendgrid/clientwith/api/send/compat/sendgrid. - Send a smoke test with the same
fromaddress production uses. - Check Tinbox's send ledger and the received SPF/DKIM/DMARC headers.
- Remove SendGrid templates, schedules, and analytics dependencies after the replacement paths are live.
Source note
This guide is validated against @sendgrid/mail and @sendgrid/client v8.1.6.
The SDK mirror test records the exact method, path, auth header, content type,
and JSON body emitted by the official packages.