Dev.to WebDev 🛠 Dev 👁 0 📖 5 min read

SendGrid Alternatives for Developers: How to Migrate Transactional Email Without Dropping a Password Reset

Picture a Friday deploy. You have swapped your email provider's SDK for a shiny new one, the tests are green, and you are halfway out the door. Then support pings: "Customers say password resets aren't arriving." You che

Picture a Friday deploy. You have swapped your email provider's SDK for a shiny new one, the tests are green, and you are halfway out the door. Then support pings: "Customers say password resets aren't arriving." You check the new provider's dashboard. Zero sends. You check the old one. Also zero, because you removed it. Somewhere between the two, a whole weekend of resets, receipts and magic links fell into the gap.

If you are comparing SendGrid alternatives ("sendgrid pricing" is one of the fastest-rising searches next to "transactional email" in Google Trends this week), the provider you pick matters less than how you move. This is a provider-agnostic migration plan for transactional email that keeps every message flowing.

Step 1: Inventory what you actually send

Before touching code, list every message your app sends and from where:

  1. Grep for the SDK and API host across every service, cron job and lambda. There is always one in a repo nobody has opened since 2022.
  2. For each message, note: trigger, template, From address, and how bad it is if it's late (password reset: very bad; weekly digest: fine).
  3. Note which features you rely on: templates, categories, event webhooks, inbound parse, IP pools.

Step 2: Export your suppressions first

This is the step people skip, and it is the one that hurts. SendGrid's suppressions documentation lists the lists it keeps on your behalf: blocks, bounces, invalid emails, spam reports, global unsubscribes and group unsubscribes. Per that page, everything except blocks keeps being suppressed. Blocks relate to your IP or content, not the address.

Your new provider knows none of this. If you don't carry the lists across, your first week on the new platform re-mails every address that hard-bounced or reported you as spam, and the new provider's reputation systems meet you at your worst. Export each list and import it into the new provider's suppression list before the first live send.

(Small bonus noted in the same doc: on SendGrid, each attempt to send to a suppressed address still consumes a credit, so cleaning your sending logic now saves money either way.)

Step 3: Authenticate the new provider in parallel

Add the new provider's DKIM keys and SPF include alongside the old ones. Don't replace anything yet.

  • Keep one SPF record: merge the new include into the existing v=spf1 record and stay under 10 DNS lookups.
  • DKIM selectors are separate per provider, so both can be live at once.
  • Confirm with a DKIM record checker before sending real mail.

Step 4: Put a router in front of both providers

Wrap sending in one function, decide per message which provider handles it, and make every send idempotent so a retry or a fallback can never send twice.

// sendEmail.js: provider-agnostic router with an idempotency key
import crypto from "node:crypto";

const ROLLOUT = Number(process.env.NEW_PROVIDER_PERCENT || 0); // 0..100

function bucket(key) {
  // stable 0..99 bucket per message key, so retries go to the same provider
  return crypto.createHash("sha256").update(key).digest()[0] % 100;
}

export async function sendEmail({ type, to, data, eventId }) {
  // eventId = the business event (order id, reset token id), not a random value
  const idempotencyKey = `${type}:${eventId}`;
  const useNew = bucket(idempotencyKey) < ROLLOUT;
  const provider = useNew ? newProvider : oldProvider;

  try {
    return await provider.send({ type, to, data, idempotencyKey });
  } catch (err) {
    if (!isRetryable(err)) throw err;
    // fall back to the other provider with the SAME key
    const fallback = useNew ? oldProvider : newProvider;
    return fallback.send({ type, to, data, idempotencyKey });
  }
}

function isRetryable(err) {
  return err.status === 429 || err.status >= 500 || err.code === "ETIMEDOUT";
}

Two details matter here. The bucket comes from the message key, so a retried reset goes to the same provider as the first attempt. And the idempotency key is derived from the business event, so even a cross-provider fallback can be de-duplicated on your side (store sent keys in your database if your providers don't de-duplicate for you). There is a longer walkthrough on how to retry a transactional email without sending it twice.

Step 5: Ramp by percentage, low-risk messages first

  1. Start at 0%, ship the router, and confirm nothing changed.
  2. Move low-stakes messages (digests, notifications) to 5%, then 25%.
  3. Watch bounces, deferrals and complaint reports on the new provider daily.
  4. Move critical messages (resets, receipts) last, once the domain has a few days of clean history there.
  5. At 100%, keep the old provider live for a couple of weeks as the fallback, then remove its SPF include and DKIM keys.

Step 6: Re-point webhooks and inbound

Event webhooks (bounces, complaints, deliveries) must feed the same suppression logic from both providers during the overlap. Verify signatures on the new webhook endpoint from day one; unsigned webhooks are an open door to someone unsubscribing your users for you.

Checklist

  • [ ] Every sending call site found and listed
  • [ ] Bounces, invalid, spam reports and unsubscribes exported and imported
  • [ ] New DKIM live, SPF merged into one record, under 10 lookups
  • [ ] Router with idempotency keys deployed at 0%
  • [ ] Low-risk messages ramped first; critical ones last
  • [ ] Webhooks from both providers feeding one suppression list
  • [ ] Old provider removed from DNS only after the overlap

Common mistakes

  • Big-bang cutover on a Friday. See the opening paragraph.
  • Forgetting suppressions. The new provider doesn't know who complained.
  • Two SPF records. That's a permerror, not twice the authentication.
  • Random idempotency keys. A UUID generated per attempt de-duplicates nothing.
  • Mixing marketing into the transactional stream during the move. Keep streams separate so a promo complaint spike can't slow down resets.

FAQ

What should I look for in a SendGrid alternative? Pricing that fits your volume, idempotent send API, signed webhooks, suppression import, separate streams for transactional and marketing, and help with SPF, DKIM and DMARC.

Can I run two email providers at once? Yes. DKIM selectors are per provider and SPF can include both, as long as it stays one record under the lookup limit.

Do I lose my sender reputation when I switch? Domain reputation travels with your domain; IP reputation doesn't. That's why you ramp gradually on the new provider.

How long should the overlap last? Long enough for a full cycle of your critical messages to run cleanly on the new provider; a couple of weeks is a sensible default.

Disclosure: I work on Email Digit. Its transactional email API for developers takes an idempotency key on every send, uses separate test and live keys, and signs and retries webhooks; developer plans start at $49 a month for 100,000 emails. The router above works with any provider. Drafted with AI assistance and checked against the sources linked above.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.