Documentation
Sign inGet started

Migrate from SendGrid

The provider-specific half of the migration guide: how SendGrid's v3 Mail Send payload, suppression lists, and Event Webhook map onto Bird. Do the steps in the main guide in order — this page is the lookup table for steps 1, 3, and 4.

Map the send call

SendGrid's POST /v3/mail/send wraps recipients in a personalizations array; Bird's POST /v1/email/messages is a flat payload, so each personalization becomes its own send (or one batch entry).
What it doesSendGridBird
Senderfrom.emailfrom
Recipientspersonalizations[].to / cc / bccto / cc / bcc (arrays, max 50 each)
Subjectsubjectsubject
Bodycontent[] (type + value)html / text (at least one)
Reply-toreply_to / reply_to_listreply_to (array, 1–25)
Custom headersheadersheaders (string → string object)
Filterable labelscategoriestags{name, value} pairs, max 20
Round-trip contextcustom_argsmetadata — arbitrary JSON, max 2 KB
Open/click trackingtracking_settingstrack_opens / track_clicks (default true)
IP poolip_pool_nameip_pool (ipp_... or ipp_shared)
Categorycategory: transactional (default) or marketing
Porting notes:
  • categories are bare strings; Bird tags are pairs. A category like "welcome" becomes {"name": "category", "value": "welcome"} — pick a stable name so your dashboards filter the way your SendGrid stats did.
  • custom_args were echoed in every event; Bird metadata is not. Webhook payloads carry email_id/recipient_id instead — fetch the message by email_id when an event handler needs your context back.
  • Dynamic templates (template_id) have no Bird equivalent yettemplate is reserved and returns 422 unsupported_feature. Render content in your application and send html/text until templates ship. The same applies to attachments and send_atscheduled_at.
  • Unsubscribe groups (asm) don't port as a concept: Bird handles list-unsubscribe at the category level — marketing mail gets suppression-aware unsubscribe handling automatically.

Export suppressions

SendGrid splits suppressions across endpoints; export each and run them through the import loop:
  • GET /v3/suppression/bounces
  • GET /v3/suppression/spam_reports
  • GET /v3/suppression/unsubscribes (global unsubscribes)
  • GET /v3/asm/groups/{group_id}/suppressions for each unsubscribe group you want to carry over

Translate webhook events

OutcomeSendGrid Event WebhookBird
Accepted/processedprocessedemail.acceptedemail.processed
Delivereddeliveredemail.delivered
Temporary failuredeferredemail.deferred
Permanent bouncebounceemail.bounced / email.out_of_band_bounce
Spam complaintspamreportemail.complained
Blocked/suppresseddroppedemail.rejected
Openopenemail.opened
Clickclickemail.clicked
Unsubscribeunsubscribe / group_unsubscribeemail.unsubscribed / email.list_unsubscribed
The droppedemail.rejected equivalence is the one to test: like SendGrid, Bird reports suppressed recipients visibly (status rejected, rejection_reason: recipient_suppressed) rather than silently dropping them, so your audit logic ports cleanly.
Verification changes more than the event names: SendGrid's Event Webhook signs with an ECDSA public key, while Bird signs per the Standard Webhooks HMAC scheme — swap your verification code for the recipe in Webhooks & events. SendGrid also batches events into JSON arrays; Bird delivers one event per request.

Cut over

Work through domains & DNS and the sandbox smoke test in the main guide — both are provider-independent.