Migrate from SendGrid to Bird.

Email is mostly DNS and IP reputation. This playbook walks the seven-section path: concept map, side-by-side send, the DKIM/SPF/DMARC dual-publish plan, the 14-day IP-reputation handoff, suppression-list import, webhook event mapping, and the 8-step cutover checklist.

SendGrid surfaces to Bird surfaces.

Most translations are mechanical. Templates, stats, and inbound parsing have direct equivalents; the suppression list comes across via CSV import.

SendGridBirdNote
sgMail.send({ from, to, subject, html })bird.emails.send({ from, to, subject, html })Same fields. Bird returns { data, error }.
sgMail.sendMultiple({ ... })bird.emails.send({ to: [...] })Array recipient. Up to 500 per call.
client.request({ url: "/v3/templates", ... })bird.templates.create/get/list/updateSame lifecycle. React Email accepted natively.
client.request({ url: "/v3/suppression/bounces" })bird.suppressions.list/importBird imports your SendGrid CSV as-is.
client.request({ url: "/v3/stats" })bird.emails.metrics({ from, to, group_by })Per-domain, per-ISP, per-IP rollups.
Event Webhook (array payload, mixed event types)bird.webhooks (one event per request, typed)Single envelope; HMAC-SHA256 signed.
Inbound Parsebird.emails.inbound (HMAC-signed parser)One handler per recipient mailbox.

The send path, both shapes.

The fields line up almost one-to-one. The biggest change is the return contract — Bird returns { data, error } destructured instead of throwing.

sendgrid-send.ts
before
import sgMail from "@sendgrid/mail";

sgMail.setApiKey(process.env.SENDGRID_API_KEY!);

try {
  const [response] = await sgMail.send({
    from:    "hello@yourdomain.com",
    to:      "ada@example.com",
    subject: "Your invite is ready",
    html:    "<p>Welcome.</p>",
    customArgs: { event_id: "evt_42" },
  });

  console.log("OK:", response.headers["x-message-id"]);
} catch (err: any) {
  console.error(err.response?.body ?? err);
  throw err;
}
bird-send.ts
after
import { BirdClient } from "@messagebird/sdk";

const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });

const { data, error } = await bird.emails.send({
  from:    "hello@yourdomain.com",
  to:      ["ada@example.com"],
  subject: "Your invite is ready",
  html:    "<p>Welcome.</p>",
  tags:    { event_id: "evt_42" },
}).safe();

if (error) throw error;
console.log("OK:", data.id);
// → "email_2bX91Yk8h..."

Dual-publish DKIM and SPF. Hold DMARC steady.

The risk during an email vendor migration is one of the records going stale and dropping alignment. Lower the TTL first, dual-publish both vendors' records for the warm-up, monitor DMARC aggregate reports, then retire the old selector.

  1. Day 0Lower the TTL on your existing SendGrid DKIM and SPF records to 300 seconds (5 minutes). Wait one full TTL cycle before continuing.
  2. Day 1Add Bird DKIM record at a separate selector (e.g. bird._domainkey) alongside the SendGrid one. Add Bird sending-IP range to your SPF include list. Both vendors now sign valid mail.
  3. Day 1–7Hold DMARC at its current policy (usually p=none or p=quarantine). Watch DMARC aggregate reports for SPF/DKIM alignment failures on either vendor.
  4. Day 7Begin ramping Bird traffic. Day 7 = 10%, Day 9 = 25%, Day 12 = 50%, Day 14 = 100%. The IP-reputation warm-up plan below runs in parallel.
  5. Day 14Remove the SendGrid DKIM selector and SPF include. Bird-only signing. DMARC alignment stays clean.
  6. Day 21Restore the TTL on the surviving records to your normal value (3600+). Decommission the SendGrid account.

Fourteen days. Bird IPs ramp; SendGrid drops.

Bird managed IPs are warmed automatically. During the handoff we route a growing share of your daily volume through Bird so the new IPs build reputation at the rate ISPs trust. Don't rush this — volume spikes on cold IPs land in spam folders.

DayBirdSendGrid
Day 15%95%
Day 210%90%
Day 315%85%
Day 420%80%
Day 525%75%
Day 630%70%
Day 740%60%
Day 850%50%
Day 960%40%
Day 1070%30%
Day 1180%20%
Day 1290%10%
Day 1395%5%
Day 14100%0%

Bring your unsubs, bounces, and spam reports with you.

Export the three CSVs from SendGrid (Global Unsubscribes, Bounces, Spam Reports) and pass them to bird.suppressions.import. The SendGrid header layout is recognized natively; no transformation needed.

import-suppressions.ts
one-shot
import { BirdClient } from "@messagebird/sdk";
import { readFileSync } from "node:fs";

const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });

// Export from SendGrid: Settings → Suppressions → Global Unsubscribes,
// Bounces, Spam Reports — download each as CSV.
const file = readFileSync("./sendgrid-suppressions.csv", "utf8");

const { data, error } = await bird.suppressions.import({
  file,
  format: "csv",
  // Bird accepts the SendGrid header layout natively.
  source: "sendgrid",
});

if (error) throw error;
console.log("Imported:", data.imported_count, "addresses");
// → "Imported: 184,221 addresses"

SendGrid Event Webhook to Bird events.

SendGrid posts an array of mixed event types per webhook. Bird posts one event per request and signs it with HMAC-SHA256 in the Bird-Signature header.

SendGrid eventBird event
processedemail.queued
deliveredemail.delivered
openemail.opened
clickemail.clicked
bounceemail.bounced
droppedemail.failed
deferredemail.queued (with reason)
spamreportemail.complained
unsubscribeemail.unsubscribed
group_unsubscribeemail.unsubscribed (with group)

Same names. Same shapes. One envelope.

Single typed event per request, HMAC-SHA256 verified through bird.webhooks.parse.

webhook.ts
signed
// SendGrid Event Webhook payload comes in as an array of events.
// Bird emits one event per webhook call. Same names; same shapes.
import { BirdClient } from "@messagebird/sdk";

const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });

export async function handler(req: Request) {
  const event = await bird.webhooks.parse(req, {
    secret: process.env.BIRD_WEBHOOK_SECRET!,
  });

  switch (event.type) {
    case "email.delivered":  return handleDelivered(event.data);
    case "email.bounced":    return handleBounce(event.data);
    case "email.opened":     return handleOpen(event.data);
    case "email.clicked":    return handleClick(event.data);
    case "email.complained": return handleComplaint(event.data);
    case "email.unsubscribed": return handleUnsub(event.data);
    default: return new Response(null, { status: 204 });
  }
}

Eight steps from domain verification to decommission.

End-to-end this takes 21 days for most senders — 14 days warm-up plus a week of cooldown. Don't compress it; deliverability damage from a rushed warm-up takes months to recover.

  1. 01Verify your sending domain on Bird and publish the DKIM record alongside the SendGrid one.
  2. 02Import your SendGrid suppression list (bounces, unsubs, complaints) via bird.suppressions.import.
  3. 03Port your templates. React Email source is accepted directly; legacy Handlebars maps to bird.templates.
  4. 04Lower the TTL on the SendGrid DKIM and SPF records 24 hours before the warm-up begins.
  5. 05Run the dual-publish DNS plan; verify both providers sign valid mail in DMARC aggregate reports.
  6. 06Begin the 14-day IP-reputation warm-up. Hold the schedule even if you have headroom — gradual builds reputation.
  7. 07Port the Event Webhook handler to bird.webhooks. Validate HMAC verification end-to-end before flipping live traffic.
  8. 08Remove the SendGrid DKIM selector and SPF include on day 14. Restore TTLs on day 21. Decommission.

Existing IP reputation is the migration tax.

If you've been on SendGrid for years and your IPs sit on Gmail's good list, you're trading something real to walk away from them. Bird managed IPs warm to comparable reputation, but the first 14 days are the cost of admission. If your volume is small (under 100K/mo), the difference is usually invisible. If you're sending millions a day, plan the warm-up around a low-stakes campaign window.

Email migrations are staffed.

Email migrations@bird.com with your sending domain, monthly volume, and current ESP. We'll provision a shared channel and a deliverability engineer for the warm-up window.

Begin met één kanaal.
Voeg de rest toe wanneer je er klaar voor bent.

Een test-API-key is direct beschikbaar. Productietoegang wordt ontgrendeld zodra je een betaalmethode toevoegt en een afzender verifieert.

Aan de slagLees de docsof

Using Claude Code, Cursor, or Codex? Point it at our hosted MCP server: curated Bird tools, a browser sign-in, and no API key. Or install the bird-ai plugin.

Cursor