| name | resend-implement |
| description | Implement a Resend email integration from scratch in your Node.js project. Step-by-step setup through production. Use when starting fresh, adding email to a new project, or need structured guidance through phases. |
| license | MIT |
| compatibility | Node.js 18+, TypeScript 4.5+, Resend SDK, RESEND_API_KEY env var required |
| metadata | {"version":"1.0.0","parent-skill":"resend-email-integration"} |
Resend Implementation Guide
A step-by-step walkthrough to integrate Resend from zero to production.
Before You Start
- Have a Resend account? → Go to resend.com, sign up, get API key
- Node.js 18+? →
node --version must be 18 or higher
- Existing project? → Must use TypeScript + either Next.js, Express, or Fastify
Phase 1: Setup (1 hour)
Step 1.1: Install Resend SDK
npm install resend
pnpm add resend
yarn add resend
Step 1.2: Add Environment Variables
Create .env.local:
RESEND_API_KEY=re_xxxxxxxxxxxx
RESEND_WEBHOOK_SECRET=whsec_xxxxxxxxxxxx
Get these from Resend dashboard:
- API Key: Settings → API Keys → Create
- Webhook Secret: Webhooks → Select endpoint → Show secret
Step 1.3: Copy EmailService Template
Next.js:
cp templates/email-service.template.ts src/lib/email.ts
Express:
cp templates/email-service.template.ts src/services/email.ts
Fastify:
cp templates/email-service.template.ts src/services/email.ts
Step 1.4: Test with @resend.dev Email
import { emailService } from '@/lib/email';
const result = await emailService.send({
to: 'delivered@resend.dev',
subject: 'Test from Resend',
html: '<p>This is a test</p>',
});
console.log(result);
Run this and verify no errors. ✅ = EmailService works
Phase 2: Integrate into Your App (2-3 hours)
Step 2.1: Create Database Table
Prisma:
model EmailLog {
id String @id @default(cuid())
emailId String @unique
to String
subject String
status String @default("pending") // sent, delivered, bounced, failed
error String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Then:
npx prisma migrate dev --name add_email_log
Step 2.2: Trigger Email on User Event
Example: Send email when user signs up
import { emailService } from '@/lib/email';
import { db } from '@/lib/db';
export async function handleUserSignup(user: User) {
const result = await emailService.send({
to: user.email,
subject: 'Welcome!',
html: `<h1>Welcome ${user.name}!</h1>`,
idempotencyKey: `user-signup-${user.id}`,
});
if (result.success) {
await db.emailLog.create({
data: {
emailId: result.id!,
to: user.email,
subject: 'Welcome!',
status: 'sent',
},
});
} else {
console.error(`Failed to send: ${result.error?.message}`);
}
}
Step 2.3: Test Sending
In your app, trigger the event (create a test user, place a test order, etc.):
- Check database → EmailLog table should have new row
- Check Resend dashboard → email should show as "Sent"
- Check recipient inbox → email should arrive (if using real email) or in @resend.dev test console
Phase 3: Implement Webhooks (2-3 hours)
Step 3.1: Create Webhook Endpoint
Next.js:
cp templates/webhook-handler.template.ts src/app/api/webhooks/resend/route.ts
Express:
cp templates/webhook-handler.template.ts src/routes/webhooks.ts
app.post('/api/webhooks/resend', handleWebhook);
Step 3.2: Deploy to Get Public URL
You need a public URL for Resend to POST to. Options:
- Vercel (if using Next.js):
vercel deploy
- ngrok (for local testing):
ngrok http 3000 → copy https://xxx.ngrok.io
- Any other hosting with a public domain
Step 3.3: Register Webhook in Resend Dashboard
- Go to resend.com → Webhooks
- Add endpoint:
https://your-domain.com/api/webhooks/resend
- Paste webhook secret
- Select events:
email.sent, email.delivered, email.bounced, email.complained
- Click "Create"
Step 3.4: Test Webhook
Click "Send test event" in Resend dashboard:
- Check server logs → should see verification + processing
- Check database EmailLog → status should update to "sent" or "delivered"
Phase 4: Monitoring (1-2 hours)
Step 4.1: Track Email Status
In your app dashboard:
export async function getEmailStats() {
return {
total: await db.emailLog.count(),
sent: await db.emailLog.count({ where: { status: 'sent' } }),
delivered: await db.emailLog.count({ where: { status: 'delivered' } }),
bounced: await db.emailLog.count({ where: { status: 'bounced' } }),
bounceRate: (bounced / total * 100).toFixed(2) + '%',
};
}
Display this on a dashboard.
Step 4.2: Alert if Bounce Rate > 4%
const stats = await getEmailStats();
if (stats.bounceRate > 4) {
console.error('🚨 Bounce rate critical:', stats.bounceRate);
}
Phase 5: Production Deployment (1 day)
Step 5.1: Verify Domain
In Resend dashboard:
- Settings → Domains → Add domain
- Copy SPF/DKIM records
- Add to your domain registrar (GoDaddy, Route53, etc.)
- Wait 15 min - 72 hours for DNS propagation
- Check "Verify" in Resend dashboard
Step 5.2: Use Verified Domain in Emails
await emailService.send({
from: 'notify@your-verified-domain.com',
to: user.email,
subject: '...',
html: '...',
});
Step 5.3: Final Checks
Step 5.4: Deploy
git push → CI/CD deploys to production
If Something Goes Wrong
| Problem | Solution |
|---|
| 429 rate limited | Backoff implemented? Check email.ts retry logic |
| Domain not verified | Check Resend dashboard, wait for DNS propagation |
| Webhook not triggered | Check webhook secret matches, HMAC verification passes |
| High bounce rate | Add bounced emails to suppressions (see webhook handler) |
| Duplicate emails | Is idempotencyKey unique? Check database for duplicates |
Success Criteria
✅ Emails send reliably (< 500ms response)
✅ Retries work (automatic on 5xx)
✅ Webhooks process (status updates in real time)
✅ Bounce rate < 4%
✅ No duplicate emails
✅ Domain verified
✅ Production monitoring in place
See resend-email-integration skill for deep reference docs.