Migrate from SendGrid
Keep your SendGrid SDK and change two lines, or adopt our SDK. Either way, the send you ship today keeps working.
Facts verified 2026-07-17 — corrections: hello@emailfast.dev
Two ways to switch
Email Fast speaks SendGrid's send API natively: SendGrid-, Mailgun-, and Postmark-compatible endpoints: point your existing SDK at a new base URL with a new key and keep your code. A successful call returns what your code already expects: an empty-body 202 with an X-Message-Id header.
Path 1 — keep @sendgrid/mail
import sgMail from "@sendgrid/mail";
+ import sgClient from "@sendgrid/client";
+ sgClient.setDefaultRequest("baseUrl", "https://api.emailfast.dev");
+ sgMail.setClient(sgClient);
- sgMail.setApiKey(process.env.SENDGRID_API_KEY);
+ sgMail.setApiKey(process.env.EMAILFAST_API_KEY); // ef_sandbox_… works everywhere
await sgMail.send({
to: "ada@example.com",
from: "you@yourdomain.com",
subject: "Welcome",
html: "<h1>Hi Ada</h1>",
+ headers: { "Idempotency-Key": "welcome-ada-1" }, // retry-safe (see below)
});Path 2 — adopt @email-fast/nodejs
- import sgMail from "@sendgrid/mail";
+ import { EmailFast } from "@email-fast/nodejs";
- sgMail.setApiKey(process.env.SENDGRID_API_KEY);
- await sgMail.send({
- to: "ada@example.com",
- from: "you@yourdomain.com",
- subject: "Welcome",
- html: "<h1>Hi Ada</h1>",
- });
+ const ef = new EmailFast({ apiKey: process.env.EMAILFAST_API_KEY, baseUrl: "https://api.emailfast.dev" });
+ await ef.send({
+ to: "ada@example.com",
+ subject: "Welcome",
+ html: "<h1>Hi Ada</h1>",
+ idempotency_key: "welcome-ada-1",
+ });What maps 1:1
- Personalizations. Each entry in
personalizationsfans out to its own message, exactly as SendGrid does. If one recipient is blocked by policy, the whole call rolls back and returns a truthful 4xx — nothing half-queued. - Templates.
template_idresolves against your imported template, so dynamic-template sends keep working after import. - The response contract.
202, empty body,X-Message-Idheader.
Every message admitted this way passes the same checkpoint as every other ingress: every send — REST, SMTP, browser SDK, compatibility endpoints, broadcasts, automations — passes through one admission gate: idempotency, suppression, quota, and content policy in a single checkpoint no ingress can skip.
What arms at launch
The hosted data importers — templates, contacts, and suppression lists pulled across from your SendGrid account — arm at launch. Today you can evaluate the whole send path with a sandbox key: sandbox keys (ef_sandbox_…) that run the real pipeline dry: real validation, real rendering, real events, a hosted capture inbox — and no email leaves.
The idempotency bonus
SendGrid's send endpoint has no idempotency. Ours does: pass an Idempotency-Key header and each recipient in a personalizations fan-out gets its own derived key, so a timeout-and-retry can never double-send part of a batch: a 202 from the API means the send is committed to a durable, partitioned outbox before we answer — a crash can't lose it, and a retry with the same idempotency key can't double-send.
Next
Questions, answered plainly
Do I need to change my application code to migrate from SendGrid?
No. POST /v3/mail/send is accepted as-is: same Bearer auth, same body shape, same 202-with-X-Message-Id response. You change the base URL and the key.
Do my SendGrid dynamic templates work?
Yes, once imported. A send that references a template_id resolves against your imported copy of that template; an unknown id returns a SendGrid-shaped 400 telling you to import it first.
How do I test before pointing production at Email Fast?
Use a sandbox key (ef_sandbox_…) in the same code. The full pipeline runs — validation, rendering, events, a hosted capture inbox — and no email leaves.