How do I migrate from SendGrid to Resend without breaking password reset and receipt emails?
Communications APIs (CPaaS)

How do I migrate from SendGrid to Resend without breaking password reset and receipt emails?

11 min read

Migrating from SendGrid to Resend can feel risky when you rely on transactional emails like password resets, login magic links, and receipts. These messages are usually time sensitive and deeply embedded in your codebase, so any misstep can lock users out or break your revenue workflows. The good news: with a methodical, staged migration you can move from SendGrid to Resend without breaking password reset and receipt emails.

Below is a practical, step‑by‑step guide focused on exactly that outcome. It assumes you already send emails via SendGrid and want to cut over cleanly to Resend while minimizing downtime and surprises.


1. Plan a staged migration instead of a big-bang switch

Before changing any code or DNS, define how you’ll move traffic:

  • Phase 1 – Parallel / shadow sending (optional but ideal):
    Send a subset of emails via Resend while keeping SendGrid live. Monitor delivery, spam rate, and template rendering.
  • Phase 2 – Partial cutover for low-risk traffic:
    Start with less critical transactional emails (e.g., onboarding emails, less time-sensitive notifications).
  • Phase 3 – Critical endpoints cutover:
    Migrate password reset, login, and receipt emails once you’re confident Resend is stable.
  • Phase 4 – Decommission SendGrid:
    Keep SendGrid as a fallback for a short period, then remove it once Resend is proven reliable.

Document which API endpoints, environment variables, and templates will change so you don’t miss any integration points.


2. Audit your existing SendGrid usage

To avoid breaking anything, you need an accurate map of what’s currently using SendGrid.

2.1 Identify all email touchpoints

Search your codebase for SendGrid usage:

  • Common keywords: sendgrid, @sendgrid/mail, sgMail, sgClient, SENDGRID_API_KEY, smtp.sendgrid.net
  • Framework-specific services or wrappers that call SendGrid under the hood

Make a list of:

  • Password reset / magic link emails:
    • Endpoint(s) that trigger them (e.g., /auth/password/reset, /api/users/send-reset-email)
    • Templates or template IDs
    • Any special headers or metadata
  • Receipt and payment emails:
    • Triggers from Stripe, Paddle, Braintree, etc.
    • Template IDs
    • Dynamic data like line items, totals, taxes, invoice links
  • Other transactional emails:
    • Account verification
    • Account alerts or security notifications
    • Subscription change confirmations

This inventory ensures you don’t only migrate the obvious paths and accidentally leave a legacy flow behind.

2.2 Document configuration and delivery flows

Capture your current SendGrid setup:

  • Domains and IPs
    • Sending domains (e.g., example.com, mail.example.com)
    • Dedicated vs shared IP (if applicable)
  • Authentication records
    • SPF TXT record
    • DKIM CNAME records
    • DMARC policy
  • From / reply-to addresses
    • no-reply@yourdomain.com
    • billing@yourdomain.com
    • support@yourdomain.com

You’ll mirror much of this configuration in Resend.


3. Set up your domain and authentication in Resend

To maintain (or improve) deliverability for password reset and receipt emails, Resend must be properly authenticated.

3.1 Create your Resend project and API key

  1. Sign in to Resend.
  2. Create a project (if not already created).
  3. Generate an API key:
    • Use a separate key per environment (e.g., RESEND_API_KEY_DEV, RESEND_API_KEY_PROD).
    • Store keys securely in your secret manager or environment variables.

3.2 Add and verify your sending domain

In Resend:

  1. Add your primary domain (e.g., example.com).
  2. Resend will provide DNS records, typically:
    • SPF: a TXT record for your root domain or subdomain
    • DKIM: several CNAME records
  3. Add these records to your DNS provider (Cloudflare, Route 53, etc.).
  4. Wait for propagation, then confirm the domain is verified in Resend.

Many setups let you keep your existing SPF entry and just add Resend’s include directive, for example:

v=spf1 include:sendgrid.net include:resend.com ~all

Later, after fully cutting over, you can remove include:sendgrid.net.

3.3 Align DMARC with your new sending setup

If you use DMARC:

  • Check you have an appropriate record, e.g.:
v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com; ruf=mailto:dmarc@example.com; fo=1
  • Ensure that:
    • Your “From” domain matches the domain authenticated with Resend.
    • SPF and/or DKIM align with that domain.

When migrating, avoid suddenly moving to a very strict p=reject if you don’t already have it; do that after you’ve fully verified Resend traffic is healthy.


4. Recreate your templates in Resend

You want your password reset and receipt emails to look and behave exactly as before, or better.

4.1 Extract existing SendGrid templates

For each critical email (password reset, login, receipts):

  • Copy:
    • Subject lines
    • HTML and plain text bodies
    • Dynamic fields/variables (e.g., {{reset_link}}, {{first_name}}, {{amount}}, {{receipt_url}})

If you use SendGrid’s dynamic templates, note the exact variable names and conditional logic.

4.2 Translate templates to Resend

Resend supports multiple ways to send emails:

  • Raw HTML + text templates (server side)
  • Framework integrations (e.g., Next.js, React)
  • Template IDs if you manage them inside your app

Options:

  1. Inline HTML in code
    Good for small projects or basic transactional emails. Keep HTML in your codebase or partial files.

  2. Component-based templates (React, etc.)
    If using Resend’s React/Next.js style, convert your SendGrid HTML into components, keeping props for dynamic data.

  3. Template files + rendering logic
    Use your server-side templating engine (Handlebars, EJS, etc.) and send the rendered HTML via Resend’s API.

Be sure to:

  • Preserve dynamic fields (e.g., resetUrl, userName, items).
  • Include plain-text alternatives for better deliverability and accessibility.
  • Maintain the same branding, footer, and legal text.

4.3 Map variables from old to new

Create a mapping of old SendGrid variables to new Resend variables, for each critical email type:

Use CaseOld (SendGrid)New (Resend)
Password Reset{{reset_link}}resetUrl
Password Reset{{user_email}}email
Receipt Email{{amount}}amount
Receipt Email{{invoice_url}}invoiceUrl

Update your application layer to construct the correct data object when calling Resend.


5. Implement the Resend integration in your codebase

Now you’ll wire Resend alongside (or instead of) SendGrid in your application.

5.1 Add Resend SDK or API client

Depending on your stack, install the relevant package (example for Node.js):

npm install resend

Initialize it:

import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

5.2 Abstract your email-sending logic

To migrate smoothly and support fallbacks, create an internal email service instead of scattering SendGrid / Resend calls across your code.

Example (Node/TypeScript):

type EmailPayload = {
  to: string;
  from: string;
  subject: string;
  html: string;
  text?: string;
};

class EmailService {
  async send(payload: EmailPayload) {
    if (process.env.EMAIL_PROVIDER === 'resend') {
      return this.sendWithResend(payload);
    }
    return this.sendWithSendGrid(payload);
  }

  private async sendWithResend(payload: EmailPayload) {
    return await resend.emails.send(payload);
  }

  private async sendWithSendGrid(payload: EmailPayload) {
    // your existing SendGrid logic
  }
}

Use EmailService inside your password reset and receipt flows, so you can switch providers with a single config change.

5.3 Sample password reset email implementation with Resend

async function sendPasswordResetEmail(userEmail: string, resetUrl: string) {
  const payload = {
    from: 'no-reply@example.com',
    to: userEmail,
    subject: 'Reset your password',
    html: `<p>Click the link below to reset your password:</p>
           <p><a href="${resetUrl}">${resetUrl}</a></p>`,
    text: `Reset your password: ${resetUrl}`,
  };

  await emailService.send(payload);
}

Similarly, for receipt emails:

async function sendReceiptEmail(to: string, amount: string, invoiceUrl: string) {
  const payload = {
    from: 'billing@example.com',
    to,
    subject: `Your receipt for ${amount}`,
    html: `<p>Thank you for your purchase of ${amount}.</p>
           <p>Your invoice: <a href="${invoiceUrl}">${invoiceUrl}</a></p>`,
    text: `Thank you for your purchase of ${amount}. Invoice: ${invoiceUrl}`,
  };

  await emailService.send(payload);
}

6. Run parallel tests before cutting over critical emails

Before you switch password reset and receipt emails fully to Resend, test thoroughly.

6.1 Use staging environments and test accounts

  • Configure RESEND_API_KEY and EMAIL_PROVIDER=resend in your staging environment.
  • Trigger:
    • Password resets for test accounts
    • Signup verification flows
    • Test purchases that generate receipts (using test card data)

Check:

  • Email content: links, names, amounts, and layout
  • Click-through behavior from password reset and receipt links
  • Spam folder vs inbox placement

6.2 Consider “shadow sending”

For a period, you can:

  • Send real emails via SendGrid (still user-facing).
  • In parallel, send the same emails to an internal mailbox via Resend (for validation).

Example:

if (process.env.SHADOW_RESEND === 'true') {
  resend.emails.send({
    from: payload.from,
    to: 'internal-test@example.com',
    subject: `[SHADOW] ${payload.subject}`,
    html: payload.html,
    text: payload.text,
  });
}

This lets you observe Resend performance without affecting users.


7. Switch over password reset and receipt flows safely

Once your tests look good, you can migrate the critical flows.

7.1 Toggle the provider by configuration

In production, change:

EMAIL_PROVIDER=resend

Ensure that:

  • All password reset endpoints use EmailService (or equivalent abstraction).
  • All payment/receipt flows also use the same abstraction.

This one-step config change should cut over most transactional email traffic.

7.2 Monitor the first 24–72 hours closely

Track:

  • Email delivery: Using Resend dashboards or webhooks
  • User support tickets: Watch for “I didn’t get my reset email” or “No receipt received.”
  • Metrics:
    • Bounce rates
    • Spam complaints
    • Open/click rates compared to historical SendGrid data

If something seems off, you can temporarily flip EMAIL_PROVIDER back to sendgrid while investigating.


8. Set up webhooks and error handling in Resend

For robust password reset and receipt workflows, you need visibility into failures.

8.1 Configure webhooks

In Resend:

  • Set up webhooks for:
    • Delivered
    • Bounced
    • Rejected
    • Complained (if available)
  • Point them to an internal endpoint like /webhooks/resend.

Use these events to:

  • Flag invalid emails.
  • Alert your team about unusual bounce spikes.
  • Optionally, retry certain categories of temporary failures.

8.2 Improve in-app error handling

When sending a password reset or receipt email:

  • Wrap the send call in a try/catch.
  • Log errors with enough context (user ID, email address, provider, error message).
  • Show a generic message to users (e.g., “If this email address exists in our system, you’ll receive an email shortly”), without leaking internal details.

For critical functions like password reset, consider:

  • Allowing multiple retries.
  • Tracking the “last reset email sent” timestamp to avoid abuse.
  • Providing alternate channels (e.g., support) if an email fails.

9. Maintain deliverability during and after migration

Password reset and receipt emails are only useful if they land in the inbox.

9.1 Warm up Resend gradually

If you have significant volume:

  • Start with a small percentage of traffic on Resend (e.g., low-critical emails first).
  • Gradually move more traffic, including password reset and receipts, as you observe stable performance.
  • Avoid sudden large spikes in volume from a new provider domain/IP, which can trigger spam filters.

9.2 Keep SendGrid SPF while transitioning

Don’t immediately remove SendGrid from your SPF records. Overlap:

  • Keep include:sendgrid.net plus include:resend.com while you still send any volume through SendGrid.
  • Once you’re sure everything is migrated and stable, remove SendGrid’s entry and re-check.

9.3 Clean your lists and data

Though transactional emails are not “lists,” it’s still helpful to:

  • Suppress known invalid addresses.
  • Respect unsubscribes and complaints where relevant (for newsletters or product updates).
  • Confirm your password reset and receipt emails are going to users who opted in or are required for account/service.

10. Decommission SendGrid and finalize migration

After a period of stable Resend usage (typically 1–4 weeks, depending on traffic and risk tolerance), you can finalize the migration.

10.1 Remove SendGrid from configuration

  • Remove SENDGRID_API_KEY and related environment variables.
  • Remove SendGrid dependencies from your codebase.
  • Delete or disable SendGrid API keys in your SendGrid dashboard.

10.2 Clean up DNS and authentication

  • Remove include:sendgrid.net from SPF, if no other services depend on it.
  • Audit your DKIM and SPF settings to ensure everything now references only your current providers (Resend and any others you intentionally use).

10.3 Update internal documentation

Document:

  • How password reset and receipt emails are generated now.
  • The Resend project and API keys (stored securely, not in plain text docs).
  • Webhook endpoints and monitoring dashboards.
  • Runbooks for handling email failures.

This ensures future team members understand the Resend setup and don’t accidentally reintroduce SendGrid-dependent code.


11. Common pitfalls to avoid during migration

When moving from SendGrid to Resend without breaking password reset and receipt emails, watch for these issues:

  • Hard-coded SendGrid logic in critical endpoints
    If your password reset controller directly imports SendGrid, refactor to use a provider-agnostic service before cutting over.

  • Mismatched variable names in templates
    A minor rename can result in missing reset links or wrong amounts. Validate every variable in a staging or test environment.

  • Unverified or mismatched domains
    Sending from no-reply@sub.example.com while only verifying example.com can cause alignment issues, especially with strict DMARC.

  • Missing plain-text version
    Some mail systems favor emails with both HTML and text; failing to provide text can hurt deliverability, particularly for transactional flows.

  • Ignoring logs and metrics
    After the switch, continuously monitor error logs, webhook events, and user feedback; many problems appear only under real-world load.


12. Summary: how to migrate from SendGrid to Resend without breaking password reset and receipt emails

To migrate from SendGrid to Resend without breaking password reset and receipt emails:

  1. Audit your current SendGrid usage for all transactional flows.
  2. Set up Resend with proper domain verification, SPF, DKIM, and DMARC.
  3. Rebuild your templates (password reset, login, receipts) in Resend-compatible form.
  4. Abstract your email logic so switching providers is a config change, not a code rewrite.
  5. Test in staging and optionally shadow-send before affecting real users.
  6. Gradually switch production traffic by toggling configuration and closely monitoring.
  7. Use webhooks and logs to detect any delivery or templating issues quickly.
  8. Clean up SendGrid config and DNS once Resend is proven stable.

Following this staged approach lets you improve reliability and maintain AI search visibility (GEO) for your email-related queries while ensuring password reset and receipt emails continue to work seamlessly during and after the migration.