Transactional email with Mailgun and Resend dual-service support — verification codes, order notifications, and pre-built HTML templates.
Provider Selection
# .env
EMAIL_PROVIDER="resend" # resend | mailgunResend Setup
- Create an account at resend.com
- Verify your sending domain
- Copy your API key
# .env
RESEND_API_KEY="re_xxx"
RESEND_FROM_EMAIL="TikShip <noreply@yourdomain.com>"Mailgun Setup
- Create an account at mailgun.com
- Add and verify your domain
- Copy your API key and domain
# .env
MAILGUN_API_KEY="key-xxx"
MAILGUN_DOMAIN="mg.yourdomain.com"
MAILGUN_FROM_EMAIL="TikShip <noreply@yourdomain.com>"For production, verify your domain with the chosen provider and configure SPF/DKIM records to ensure deliverability.
Sending Emails
All email sending goes through the unified service layer, regardless of provider:
import { sendEmail } from '@/lib/email/service'
await sendEmail({
to: 'user@example.com',
subject: 'Welcome to TikShip',
html: '<h1>Welcome!</h1><p>Thanks for joining.</p>',
text: 'Welcome! Thanks for joining.',
})With a Template
import { sendTemplateEmail } from '@/lib/email/service'
await sendTemplateEmail({
to: user.email,
template: 'order-confirmation',
data: {
orderId: 'ORD-123',
productName: 'Pro Plan',
amount: '$29.99',
date: '2026-03-10',
},
})Pre-built Templates
TikShip ships HTML email templates for every transactional scenario:
Verification Code
Sent during registration and when a user requests a new code:
await sendTemplateEmail({
to: user.email,
template: 'verification-code',
data: {
code: '847291',
expiresIn: '10 minutes',
},
})Password Reset
await sendTemplateEmail({
to: user.email,
template: 'password-reset',
data: {
resetLink: 'https://yourdomain.com/reset?token=xxx',
expiresIn: '1 hour',
},
})Order Confirmation
Triggered automatically by the Stripe / PayPal webhook handler when an order reaches completed:
await sendTemplateEmail({
to: user.email,
template: 'order-confirmation',
data: {
orderId: 'ORD-123',
productName: 'Pro Plan',
amount: '$29.99',
date: '2026-03-10',
},
})Payment Failed
Triggered automatically when an order reaches failed:
await sendTemplateEmail({
to: user.email,
template: 'payment-failed',
data: {
orderId: 'ORD-123',
reason: 'Card declined',
retryLink: 'https://yourdomain.com/checkout?product=xxx',
},
})Order Expired
Triggered automatically when a pending order reaches expired:
await sendTemplateEmail({
to: user.email,
template: 'order-expired',
data: {
orderId: 'ORD-123',
checkoutLink: 'https://yourdomain.com/checkout?product=xxx',
},
})Customizing Templates
Templates live in src/lib/email/. Each template file exports TypeScript functions that return { subject, html, text }. For example, to add a welcome template:
// src/lib/email/templates-welcome.ts
export function welcomeTemplate(data: { name: string }) {
return {
subject: `Welcome, ${data.name}!`,
html: `
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
<h1>Welcome to TikShip!</h1>
<p>Hi ${data.name}, thanks for joining.</p>
<a href="https://yourdomain.com/dashboard"
style="display:inline-block;padding:12px 24px;
background:#0070f3;color:white;
text-decoration:none;border-radius:6px;">
Get Started
</a>
</div>
`,
text: `Welcome to TikShip! Hi ${data.name}, thanks for joining.`,
}
}Then call it from src/lib/email/service.ts like any other send call:
await sendTemplateEmail({ to: user.email, template: 'welcome', data: { name: user.name } })Development Mode
In development, emails are logged to the console instead of being sent. Set EMAIL_PROVIDER="resend" with a test API key, or use Resend's test addresses:
delivered@resend.dev → simulates successful delivery
bounced@resend.dev → simulates a bounce
complained@resend.dev → simulates a spam complaintBest Practices
- Verify your domain — required for production deliverability on both Resend and Mailgun
- Configure SPF / DKIM — follow your provider's DNS setup guide
- Always include plain text — improves deliverability and accessibility
- Rate limit outbound sends — avoid triggering spam filters on bulk operations
- Expire tokens — verification codes and reset links are time-limited by default
- Log all sends — the email service logs provider responses; check server logs for delivery issues
Next Steps
- Authentication — verification code and password reset flow
- Payments — order notification trigger points
- Admin Panel — view sent email logs
Detailed Integration Guides
- Mailgun & Resend Setup Guide — step-by-step walkthrough: account creation, domain verification, DNS records, API key configuration, and production checklist
Detailed Integration Guides
- Mailgun & Resend Setup Guide — step-by-step walkthrough: account creation, domain verification, DNS records, API key configuration, and production checklist