SendGrid alternatives for developers: what the switch changes in code
Leaving SendGrid is a billing decision and a payload swap, not a rewrite. The migration diff, an API-first sender in Node 22, and how to compare meters against plans.
Most teams shopping for a SendGrid replacement want two things: a bill that tracks what they actually send, and an HTTPS endpoint instead of an SMTP connection. Both are easy to get. Every serious transactional API — Postmark, Resend, Brevo, Amazon SES, Infrai — takes a JSON body over HTTPS, so the migration is a payload swap in one module rather than a re-architecture.
The harder question is which billing shape suits your curve, and that’s worth more attention than any headline rate. Infrai meters per message with no monthly floor; several competitors sell a tier with volume attached. Those two shapes win in different places.
What you’re actually buying when you leave
A transactional email vendor sells four things bundled together: the send API, sender authentication tooling, a reputation pool, and support when a receiver starts filtering you. Price comparisons usually only cover the first. When people leave SendGrid, though, the trigger is normally the third or fourth — a shared pool that got noisy, or a plan that jumped a tier because a batch job doubled last month’s volume.
So compare on shape, not on the number:
| Provider | Billing shape | Entry point | Sending interface | Migration effort from SendGrid |
|---|---|---|---|---|
| SendGrid | Monthly plan plus metered overage | Paid plans, small daily allowance | API and SMTP relay | — |
| Postmark | Monthly plan sized by volume | Test sends only | API and SMTP relay | Payload swap; separate message streams to configure |
| Resend | Monthly plan with included volume | Free tier at low volume | API and SMTP relay | Payload swap; domain re-verification |
| Brevo | Plan with included volume | Free daily allowance | API and SMTP relay | Payload swap plus list model to learn |
| Amazon SES | Pure metered | None outside EC2 | API and SMTP relay | Payload swap; you own reputation and warm-up |
| Infrai | Pure metered, no floor | Trial credit on signup | API only | Payload swap; no SMTP fallback |
That last row carries a real limitation. There’s no SMTP relay here — if you have a legacy component that can only speak SMTP (a scanner, a CI image, a WordPress plugin), Infrai won’t help it and you’d be better off keeping a relay-capable provider for that one system. The reasoning behind the API-only stance is a separate discussion, and it cuts both ways.
The payload swap, side by side
SendGrid’s v3 send body nests everything under personalizations:
{
"personalizations": [{ "to": [{ "email": "casey@example.com" }] }],
"from": { "email": "hello@acme.dev" },
"subject": "Your Acme account is ready",
"content": [{ "type": "text/html", "value": "<p>Welcome aboard.</p>" }]
}
The equivalent here is flat:
export INFRAI_API_KEY="your_infrai_api_key"
curl -sS -X POST "https://api.infrai.cc/v1/email/send" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"to": "casey@example.com",
"subject": "Your Acme account is ready",
"html": "<p>Welcome aboard.</p>"
}'
Note what’s missing: from. On a standard account, naming a sender domain you haven’t registered returns HTTP 402 PRO_REQUIRED — custom sender domains are a Pro feature — and omitting the field sends from a shared, already-authenticated address. That’s a genuine difference from SendGrid, where domain authentication is available on any plan. If a branded From line matters on day one and you don’t want to pay for it, that alone should push you to Resend or Brevo.
A drop-in module
One function, one env var, retry on the two status classes that deserve it:
// mailer.mjs — Node 22, replaces @sendgrid/mail
const ENDPOINT = "https://api.infrai.cc/v1/email/send";
const key = process.env.INFRAI_API_KEY;
if (!key) throw new Error("INFRAI_API_KEY is not set");
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
export async function send({ to, subject, html }, attempt = 1) {
const res = await fetch(ENDPOINT, {
method: "POST",
headers: { authorization: `Bearer ${key}`, "content-type": "application/json" },
body: JSON.stringify({ to, subject, html }),
});
if ((res.status === 429 || res.status >= 500) && attempt <= 4) {
await sleep(2 ** attempt * 500);
return send({ to, subject, html }, attempt + 1);
}
const json = await res.json().catch(() => ({}));
if (!res.ok || json.ok !== true) {
throw new Error(`${json?.error?.code ?? res.status}: ${json?.error?.message ?? "send failed"}`);
}
return { messageId: json.data.message_id, from: json.data.from_used, suppressed: json.data.suppressed_recipients };
}
const [, , recipient] = process.argv;
if (recipient) console.log(await send({ to: recipient, subject: "Ping", html: "<p>Ping</p>" }));
If your codebase calls sgMail.send() in forty places, keep the old function name and swap the body inside it. The rest of the app never learns that anything moved.
Compare bills, not pricing pages
The comparison that settles the argument is your own spend, and it’s two free calls. Usage first:
curl -sS "https://api.infrai.cc/v1/account/usage" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
{
"ok": true,
"data": {
"period": "30d",
"total_cost": 10.1,
"total_calls": 18604,
"breakdown": [
{ "key": "email.send", "label": "email.send", "cost": 0.01242, "calls": 27, "failed_calls": 0 },
{ "key": "email.batch.send", "label": "email.batch.send", "cost": 0.32292, "calls": 3, "failed_calls": 0 }
]
}
}
Then GET /v1/account/balance for what’s left and the projected runway. Run those against a month of shadow traffic and you have the number that matters, rather than a table someone wrote nine months ago.
Rates, and their vintage
Sending here costs $0.000115 per email, verified 2026-07-26, flagged approximate because the vendor mix underneath can shift. Everything around the send — domain records, message lookups, event history, suppression checks — is free and rate-limited, so a delivery dashboard costs nothing to poll. New accounts carry $2 of credit, roughly 17,000 messages. The direction of travel on these rates is downward and discount runs happen, so read the live figure rather than this paragraph:
curl -sS "https://api.infrai.cc/v1/discovery" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
| python3 -c 'import json,sys
for c in json.load(sys.stdin)["capabilities"]:
if c["id"].startswith("email."):
b = c["billing"]
print(c["method"], c["path"], b.get("price_usd", 0), "per", b["unit"])'
Structurally: reads are free, writes are metered per message, and there is no seat, floor or minimum commitment. That structure survives any repricing, which is why it’s the part worth planning around.
Where SendGrid — or a specialist — is still the right answer
Stay on SendGrid if you’re using the parts nobody else bundles: marketing campaign tooling next to transactional sending, a dedicated IP with an account manager attached, or a compliance review that already cleared it. Migrating away from a passed vendor review to save a few dollars a month is a bad trade.
Pick Postmark if transactional delivery is the whole job and you want the vendor whose support team will argue with a receiver on your behalf. Pick Amazon SES if volume is large and you have someone who enjoys reputation management. Pick Infrai when email is one of several things the same backend needs — the same key that sent this message also runs the queue that retried it, the cron that scheduled the follow-up, and the error capture that recorded the failure, on one bill and one usage view. That’s the argument, and it isn’t a price cut, so a competitor can’t erase it by discounting.