$ migrate
Moving from SendGrid to Pharos
Nothing here requires a maintenance window. SPF includes and DKIM selectors coexist, so your domain can be authenticated for both providers while you move traffic across in whatever increments you like. The part that does need care is the suppression list, because SendGrid keeps it in five places.
A SendGrid migration is four separate jobs: export five suppression lists rather than one, add your own authentication records alongside theirs, move the send call, and repoint the event webhook. Both providers can be authenticated at the same time, so none of it needs a big-bang cutover.
The order the work happens in.
-
01
Export the suppression list
Pull every suppressed address out of SendGrid before anything else changes.
-
02
Verify the domain on Pharos
Add four DNS records alongside the ones already there. Both providers stay authenticated.
-
03
Move the send call
Point the application at POST /v1/send or the SMTP relay, behind whatever flag you use.
-
04
Ramp the traffic
Move a share of sending across, watch the events, increase it. No cutover window is required.
-
05
Repoint the webhook and clean up DNS
Move the event consumer, then remove the SendGrid records once nothing depends on them.
Steps two and three are reversible and can overlap for as long as you like. An SPF record may name both providers, DKIM selectors are independent of one another, and the single DMARC record does not change at any point. That is what makes a phased migration possible: there is no moment where the domain is authenticated for the new provider and not the old one.
The suppression list is five lists, and exporting one of them is the common mistake.
Bounces, blocks, spam reports, invalid emails and global unsubscribes are five separate endpoints under /v3/suppression/. A migration that pulls bounces alone leaves the other four behind, and the first send on the new provider mails every address that was on them — including the people who pressed the spam button. Those addresses are the ones most likely to press it again, which is how a clean new setup earns a complaint rate in week one.
Step 1 — export the suppression list.
Five endpoints, each paginated at a maximum of 500 records a page. Loop the offset until a page comes back short. If your account uses subusers, every subuser has its own suppression lists and needs its own pass with the on-behalf-of header — the parent account key does not return them.
# every list, one page at a time, 500 at a time
for list in bounces blocks spam_reports invalid_emails; do
offset=0
while :; do
page=$(curl -sG "https://api.sendgrid.com/v3/suppression/$list" \
-H "Authorization: Bearer $SENDGRID_API_KEY" \
-d "limit=500" -d "offset=$offset")
[ "$(echo "$page" | jq 'length')" -eq 0 ] && break
echo "$page" | jq -r '.[].email' >> suppressions.txt
offset=$((offset + 500))
done
done
# global unsubscribes live on their own path
curl -sG "https://api.sendgrid.com/v3/suppression/unsubscribes" \
-H "Authorization: Bearer $SENDGRID_API_KEY" \
-d "limit=500" | jq -r '.[].email' >> suppressions.txt
sort -u suppressions.txt > suppressions-deduped.txt Import the deduplicated file into the Pharos suppression list before the first send, not after it. Pharos adds to that list automatically on every hard bounce and every complaint, but it cannot know about the four years of them that happened somewhere else.
Pharos suppresses an address automatically on a hard bounce and on a complaint, keeps the list per account, and lets you export it again whenever you like — including on the way out, if this page ever needs writing in the other direction. The difference between a hard and a soft bounce decides which addresses end up on it.
Step 2 — what is in DNS now, and what gets added.
With automated security on — the default — SendGrid authenticates your domain with three CNAMEs pointing into sendgrid.net, plus a DMARC TXT record. Nothing below needs to be deleted while you migrate. Read it so you know which records are theirs when the time comes to remove them.
The SendGrid records
| Type | Host | Value | What it does |
|---|---|---|---|
| CNAME | em1234.yourdomain.com | u1234567.wl123.sendgrid.net | The return path. Carries their SPF and the bounce address, which is why bounce notices go to them. |
| CNAME | s1._domainkey.yourdomain.com | s1.domainkey.u1234567.wl123.sendgrid.net | DKIM. The selector is s1, so a Pharos selector does not collide with it. |
| CNAME | s2._domainkey.yourdomain.com | s2.domainkey.u1234567.wl123.sendgrid.net | The second DKIM key, for rotation. |
| TXT | _dmarc.yourdomain.com | v=DMARC1; p=none; | One DMARC record serves every provider. Leave it exactly as it is during the move. |
If automated security is off, you have an MX record on em1234.yourdomain.com pointing at mx.sendgrid.net and a TXT SPF record with include:sendgrid.net instead. In that case the include is in your own SPF record, and removing it at the end is an edit rather than a deletion — check the ten-lookup budget after you add the Pharos include and before you remove theirs.
The four Pharos records
Added alongside, never instead of. Pharos verifies all four before the first send and re-checks them daily, so a record quietly edited six months from now surfaces as a warning rather than as a delivery failure.
| Type | Host | Value | Note |
|---|---|---|---|
| TXT | yourdomain.com | v=spf1 include:pharosinfra.net ~all | Merged into the SPF record you already have — one TXT record per domain, never two. |
| TXT | ph1._domainkey.yourdomain.com | v=DKIM1; k=rsa; p=MIIBIj… | The ph1 selector. It does not collide with the selector your current provider signs with. |
| CNAME | bounce.yourdomain.com | bounce.pharosinfra.net | The aligned return path, so the envelope sender is on your domain rather than the gateway’s. |
| TXT | _dmarc.yourdomain.com | v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com | One record per domain. If you already have one, it does not change during a migration. |
One thing to check before you add the include: SPF permits ten DNS lookups per evaluation, and two providers named in one record is two includes' worth of them. If you are already close, adding the second one is what breaks it — and it breaks silently. The ten-lookup limit explains how to count yours.
Steps 3 and 4 — move the call, then the traffic.
The send call is plain HTTP. There is no SDK to install and nothing to import, which means the change in your application is a URL, a header and a body shape rather than a dependency.
curl -X POST https://api.pharos.email/v1/send \
-H "Authorization: Bearer $PHAROS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-4471" \
-d '{
"from": "receipts@yourdomain.com",
"to": "customer@example.com",
"subject": "Your receipt",
"html": "<p>Thanks for your order.</p>"
}' If you would rather not touch application code at all, point your existing mailer at the SMTP relay on smtp.pharosinfra.net:587 with STARTTLS. Same pipeline, same events, and a migration that is four lines of configuration. The Idempotency-Key header matters more during a migration than at any other time: a retry across a half-switched deployment is exactly the situation that sends a password reset twice.
Then ramp. Send a percentage, read the events, raise it. There is no warmup requirement to satisfy here — the sending IPs are dedicated to Pharos and already have a reputation — but ramping is still how you find the template that renders wrong or the address list nobody had cleaned, at a volume where finding it is cheap.
Step 5 — the event webhook.
SendGrid posts an array of events to one webhook URL. Pharos posts one event per request and also exposes the same events on GET /v1/events for 120 days, so a consumer that missed a delivery can ask rather than wait for a retry. The names do not map one to one — theirs describe their pipeline, and two of them have no equivalent because Pharos has no marketing feature to generate them.
| SendGrid | Pharos | Note |
|---|---|---|
| processed | accepted-by-system | Queued after internal checks passed. |
| dropped | rejected-by-system | Refused before it reached the network. On Pharos this is usually the suppression list. |
| delivered | delivered | The receiving server accepted it. |
| deferred | — | Pharos retries internally and does not emit a per-attempt event. The outcome arrives as delivered or failed. |
| bounce | failed | Permanent failure. Also adds the address to suppression. |
| blocked | rejected-by-mta | The sending infrastructure refused it. |
| open | opened | Only when open tracking is on. |
| click | clicked | Only when link tracking is on. |
| spamreport | complained | Counts against the 0.1% complaint rate in the AUP. |
| unsubscribe | unsubscribed | The recipient opted out. |
| group_unsubscribe | — | Unsubscribe groups are a marketing-list feature. Pharos carries transactional mail only and has no equivalent. |
Handle the unknown-event case before you switch: a consumer written against a fixed list of SendGrid names will throw on accepted-by-mta, which arrives on every message.
A dash means Pharos emits nothing equivalent. In every case on this page that is because the event describes something Pharos does not do, rather than something it does quietly — the full list is in the events reference, and what is worth alerting on is in what to monitor on a transactional sender.
When to stay on SendGrid.
Four reasons not to do any of the above. They are here because you would find them during the migration otherwise, and finding them then costs a great deal more than reading them now.
-
You use Marketing Campaigns
Pharos sends transactional mail only — the AUP says so, and the complaint threshold is written for it. A campaign builder, contact lists and segmentation have no equivalent here, and asking for one is asking for a different product.
-
You depend on subusers
Subusers give one account separate sending identities with separate reputations and separate API keys. Pharos scopes keys to a verified sending domain rather than to a sub-identity, which covers some of the same ground and not all of it. If you resell sending to your own customers, check this before anything else.
-
You use Inbound Parse
Receiving mail and posting it to your application is not something Pharos does. That stays where it is, or moves somewhere else, and it is worth deciding which before you delete an MX record.
-
Your volume is genuinely enormous
Pharos is a young gateway. If you are sending at a scale where a provider’s own capacity planning is your capacity planning, say so in the first email rather than discovering it at cutover.
Pharos carries transactional mail only — mail a recipient's own action caused. That single constraint is the reason for most of the gaps above, and it is written down in the acceptable use policy rather than discovered at suspension time.
Moving from somewhere else.
-
Moving from Postmark to Pharos
Dump suppressions per message stream, keep complaint suppressions permanent, move the transactional stream only, and map the webhooks. Step by step.
-
Moving from Mailgun to Pharos
Export bounces, unsubscribes and complaints per domain, leave the MX records alone until routes are handled, and retire the tracking CNAME last.
Nothing about this needs a maintenance window.
The free plan carries every feature and needs no card, which is enough to verify a domain and send the first test alongside whatever you run today.
start free