Skip to content

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 settingTinbox replacement
SENDGRID_API_KEY=SG...TINBOX_API_KEY=tbx_..., or keep the old env name temporarily with a Tinbox key
https://api.sendgrid.comhttps://<your-tinbox-host>/api/send/compat/sendgrid
sgMail.setApiKey(process.env.SENDGRID_API_KEY)sgMail.setApiKey(process.env.TINBOX_API_KEY)
SendGrid Email API logsTinbox send ledger and delivery events
SendGrid verified sender/domainTinbox 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"
UsageMigration path
sgMail.send with from, to, subject, html, text, cc, bcc, replyTo, headers, base64 attachmentsKeep the SendGrid SDK
personalizations[] for one rendered message shapeSupported for the first personalization; extras warn
template_id, dynamic_template_dataRender the template in your app, then send html/text
send_atSchedule in your app or queue before calling Tinbox
ASM, categories, tracking settings, substitution systemsMove 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: "&#x3C;p>Thanks.&#x3C;/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 fieldTinbox supportNotes
fromYesEnforced against the Tinbox key's sender scope.
personalizations[0].to, cc, bccYesExtra personalizations are accepted with a warning.
top-level to, cc, bcc via @sendgrid/mailYesThe SDK normalizes these before sending.
subjectYesTop-level or first personalization.
content, html, textYesThe SDK emits content[]; Tinbox accepts HTML and plain text parts.
reply_to / replyToYesSDK normalizes camelCase.
headersPartialCustom X-* headers only.
attachments[].contentYesBase64 content.
custom_args.tinbox_idempotency_keyPartialUsed as an app-level idempotency hint where configured.
template_id, dynamic_template_dataNoRender before calling Tinbox.
send_atNoSchedule outside the compat route.
asm, categories, mail_settings, tracking_settingsWarningAccepted 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: "&#x3C;p>Thanks.&#x3C;/p>",
  idempotencyKey: "receipt-4821",
});

Cutover checklist

  1. Verify the Tinbox sending domain.
  2. Create a Tinbox sending key scoped to the production from address.
  3. Configure @sendgrid/client with /api/send/compat/sendgrid.
  4. Send a smoke test with the same from address production uses.
  5. Check Tinbox's send ledger and the received SPF/DKIM/DMARC headers.
  6. 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.