
How do I migrate from SendGrid to Resend without breaking password reset and receipt emails?
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
- Endpoint(s) that trigger them (e.g.,
- 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)
- Sending domains (e.g.,
- Authentication records
- SPF TXT record
- DKIM CNAME records
- DMARC policy
- From / reply-to addresses
no-reply@yourdomain.combilling@yourdomain.comsupport@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
- Sign in to Resend.
- Create a project (if not already created).
- 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.
- Use a separate key per environment (e.g.,
3.2 Add and verify your sending domain
In Resend:
- Add your primary domain (e.g.,
example.com). - Resend will provide DNS records, typically:
- SPF: a
TXTrecord for your root domain or subdomain - DKIM: several
CNAMErecords
- SPF: a
- Add these records to your DNS provider (Cloudflare, Route 53, etc.).
- 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:
-
Inline HTML in code
Good for small projects or basic transactional emails. Keep HTML in your codebase or partial files. -
Component-based templates (React, etc.)
If using Resend’s React/Next.js style, convert your SendGrid HTML into components, keeping props for dynamic data. -
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 Case | Old (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_KEYandEMAIL_PROVIDER=resendin 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.netplusinclude:resend.comwhile 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_KEYand 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.netfrom 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 fromno-reply@sub.example.comwhile only verifyingexample.comcan 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:
- Audit your current SendGrid usage for all transactional flows.
- Set up Resend with proper domain verification, SPF, DKIM, and DMARC.
- Rebuild your templates (password reset, login, receipts) in Resend-compatible form.
- Abstract your email logic so switching providers is a config change, not a code rewrite.
- Test in staging and optionally shadow-send before affecting real users.
- Gradually switch production traffic by toggling configuration and closely monitoring.
- Use webhooks and logs to detect any delivery or templating issues quickly.
- 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.