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:

ProviderBilling shapeEntry pointSending interfaceMigration effort from SendGrid
SendGridMonthly plan plus metered overagePaid plans, small daily allowanceAPI and SMTP relay
PostmarkMonthly plan sized by volumeTest sends onlyAPI and SMTP relayPayload swap; separate message streams to configure
ResendMonthly plan with included volumeFree tier at low volumeAPI and SMTP relayPayload swap; domain re-verification
BrevoPlan with included volumeFree daily allowanceAPI and SMTP relayPayload swap plus list model to learn
Amazon SESPure meteredNone outside EC2API and SMTP relayPayload swap; you own reputation and warm-up
InfraiPure metered, no floorTrial credit on signupAPI onlyPayload 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.

References

Browse more email developer guides