Email System
Learn how to configure Resend for transactional emails, customize email templates, and ensure reliable delivery.
RankThis uses Resend for reliable transactional email delivery with branded templates and automated workflows.
✅ Email Types
- • Welcome emails for new users
- • Magic link authentication
- • Subscription confirmation
- • Payment success/failure
- • Waitlist notifications
- • Plan change confirmations
🔧 Features
- • Branded email templates
- • Dynamic content injection
- • Delivery tracking
- • Domain verification
- • Error handling
- • Rate limiting protection
Resend Setup
- 1. Sign up at resend.com
- 2. Verify your email address
- 3. Create an API key in the dashboard
- 4. Set up domain verification (recommended)
- 5. Configure your sending domain
📧 Domain Setup
Verify your domain in Resend to improve deliverability and remove the "via resend.dev" label from emails.
Add your Resend configuration to environment variables:
# Resend Email ConfigurationRESEND_API_KEY="re_..." # Get from Resend dashboardFROM_EMAIL="noreply@yourdomain.com" # Your verified sending address# Company Information (used in email templates)COMPANY_NAME="Your SaaS"SUPPORT_EMAIL="support@yourdomain.com"COMPANY_URL="https://yourdomain.com"
Use your verified domain for FROM_EMAIL to ensure good deliverability.
Email Service Implementation
The email service handles all transactional email sending:
1import { Resend } from "resend";23const resend = new Resend(process.env.RESEND_API_KEY!);45interface EmailOptions {6 to: string;7 subject: string;8 html: string;9 from?: string;10}1112export async function sendEmail({ to, subject, html, from }: EmailOptions) {13 try {14 const { data, error } = await resend.emails.send({15 from: from ?? `${process.env.COMPANY_NAME} <${process.env.FROM_EMAIL}>`,16 to: [to],17 subject,18 html,19 });2021 if (error) {22 console.error("Email send error:", error);23 throw new Error(`Failed to send email: ${error.message}`);24 }2526 console.log("Email sent successfully:", data?.id);27 return data;28 } catch (error) {29 console.error("Email service error:", error);30 throw error;31 }32}3334// Welcome email35export async function sendWelcomeEmail(userEmail: string, userName?: string) {36 const html = generateWelcomeEmailHTML(userName);3738 return sendEmail({39 to: userEmail,40 subject: `Welcome to ${process.env.COMPANY_NAME}!`,41 html,42 });43}4445// Subscription confirmation46export async function sendSubscriptionConfirmation(47 userEmail: string,48 planName: string,49 amount: string50) {51 const html = generateSubscriptionEmailHTML(planName, amount);5253 return sendEmail({54 to: userEmail,55 subject: "Subscription Confirmed",56 html,57 });58}
Email Templates
Branded welcome email sent to new users:
1export function generateWelcomeEmailHTML(userName?: string) {2 const name = userName ?? "there";34 return `5 <!DOCTYPE html>6 <html>7 <head>8 <meta charset="utf-8">9 <meta name="viewport" content="width=device-width, initial-scale=1.0">10 <title>Welcome to ${process.env.COMPANY_NAME}</title>11 </head>12 <body style="margin: 0; padding: 0; background-color: #f7fafc; font-family: system-ui, sans-serif;">13 <table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f7fafc; padding: 40px 0;">14 <tr>15 <td align="center">16 <table width="600" cellpadding="0" cellspacing="0" style="background-color: white; border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);">17 <!-- Header -->18 <tr>19 <td style="padding: 40px 40px 20px; text-align: center; border-bottom: 1px solid #e2e8f0;">20 <h1 style="margin: 0; color: #1a202c; font-size: 28px; font-weight: bold;">21 Welcome to ${process.env.COMPANY_NAME}!22 </h1>23 </td>24 </tr>2526 <!-- Content -->27 <tr>28 <td style="padding: 40px;">29 <p style="margin: 0 0 20px; color: #4a5568; font-size: 16px; line-height: 1.6;">30 Hi ${name},31 </p>3233 <p style="margin: 0 0 20px; color: #4a5568; font-size: 16px; line-height: 1.6;">34 Welcome to ${process.env.COMPANY_NAME}! We're excited to have you on board.35 </p>3637 <p style="margin: 0 0 30px; color: #4a5568; font-size: 16px; line-height: 1.6;">38 You can now access your account and explore all the features we have to offer.39 </p>4041 <!-- CTA Button -->42 <table cellpadding="0" cellspacing="0" style="margin: 0 auto;">43 <tr>44 <td style="background-color: #4299e1; border-radius: 6px; padding: 12px 24px;">45 <a href="${process.env.COMPANY_URL}/account"46 style="color: white; text-decoration: none; font-weight: 600; font-size: 16px;">47 Go to Dashboard48 </a>49 </td>50 </tr>51 </table>52 </td>53 </tr>5455 <!-- Footer -->56 <tr>57 <td style="padding: 20px 40px; text-align: center; border-top: 1px solid #e2e8f0; background-color: #f7fafc;">58 <p style="margin: 0; color: #718096; font-size: 14px;">59 Need help? Reply to this email or contact us at60 <a href="mailto:${process.env.SUPPORT_EMAIL}" style="color: #4299e1;">${process.env.SUPPORT_EMAIL}</a>61 </p>62 </td>63 </tr>64 </table>65 </td>66 </tr>67 </table>68 </body>69 </html>70 `;71}
Payment confirmation email for successful subscriptions:
1export function generateSubscriptionEmailHTML(planName: string, amount: string) {2 return `3 <!DOCTYPE html>4 <html>5 <head>6 <meta charset="utf-8">7 <meta name="viewport" content="width=device-width, initial-scale=1.0">8 <title>Subscription Confirmed</title>9 </head>10 <body style="margin: 0; padding: 0; background-color: #f7fafc; font-family: system-ui, sans-serif;">11 <table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f7fafc; padding: 40px 0;">12 <tr>13 <td align="center">14 <table width="600" cellpadding="0" cellspacing="0" style="background-color: white; border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);">15 <!-- Header -->16 <tr>17 <td style="padding: 40px 40px 20px; text-align: center; border-bottom: 1px solid #e2e8f0;">18 <div style="display: inline-flex; align-items: center; justify-content: center; width: 60px; height: 60px; background-color: #48bb78; border-radius: 50%; margin-bottom: 20px;">19 <span style="color: white; font-size: 24px;">✓</span>20 </div>21 <h1 style="margin: 0; color: #1a202c; font-size: 28px; font-weight: bold;">22 Subscription Confirmed!23 </h1>24 </td>25 </tr>2627 <!-- Content -->28 <tr>29 <td style="padding: 40px;">30 <p style="margin: 0 0 20px; color: #4a5568; font-size: 16px; line-height: 1.6;">31 Thank you for subscribing to ${process.env.COMPANY_NAME}!32 </p>3334 <!-- Plan Details -->35 <table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f7fafc; border-radius: 6px; margin: 20px 0;">36 <tr>37 <td style="padding: 20px;">38 <h3 style="margin: 0 0 10px; color: #2d3748; font-size: 18px;">Plan Details</h3>39 <p style="margin: 0; color: #4a5568; font-size: 16px;">40 <strong>Plan:</strong> ${planName}<br>41 <strong>Amount:</strong> ${amount}42 </p>43 </td>44 </tr>45 </table>4647 <p style="margin: 0 0 30px; color: #4a5568; font-size: 16px; line-height: 1.6;">48 Your subscription is now active and you have access to all premium features.49 </p>5051 <!-- CTA Button -->52 <table cellpadding="0" cellspacing="0" style="margin: 0 auto;">53 <tr>54 <td style="background-color: #4299e1; border-radius: 6px; padding: 12px 24px;">55 <a href="${process.env.COMPANY_URL}/account"56 style="color: white; text-decoration: none; font-weight: 600; font-size: 16px;">57 Manage Subscription58 </a>59 </td>60 </tr>61 </table>62 </td>63 </tr>6465 <!-- Footer -->66 <tr>67 <td style="padding: 20px 40px; text-align: center; border-top: 1px solid #e2e8f0; background-color: #f7fafc;">68 <p style="margin: 0; color: #718096; font-size: 14px;">69 Questions? Contact us at70 <a href="mailto:${process.env.SUPPORT_EMAIL}" style="color: #4299e1;">${process.env.SUPPORT_EMAIL}</a>71 </p>72 </td>73 </tr>74 </table>75 </td>76 </tr>77 </table>78 </body>79 </html>80 `;81}
Email Integration
Magic link emails are automatically sent by NextAuth.js:
1// NextAuth configuration with Resend2import Resend from "next-auth/providers/resend";34export const { handlers, signIn, signOut, auth } = NextAuth({5 providers: [6 Resend({7 apiKey: process.env.RESEND_API_KEY,8 from: process.env.FROM_EMAIL,9 }),10 ],11 // ... other config12});1314// Custom magic link email template (optional)15Resend({16 apiKey: process.env.RESEND_API_KEY,17 from: process.env.FROM_EMAIL,18 sendVerificationRequest: async ({ identifier: email, url }) => {19 await sendEmail({20 to: email,21 subject: `Sign in to ${process.env.COMPANY_NAME}`,22 html: generateMagicLinkEmailHTML(url),23 });24 },25})
Send emails based on Stripe webhook events:
1// In your Stripe webhook handler2export async function handleStripeWebhook(event: Stripe.Event) {3 switch (event.type) {4 case "customer.subscription.created":5 const subscription = event.data.object as Stripe.Subscription;6 const customer = await stripe.customers.retrieve(7 subscription.customer as string8 ) as Stripe.Customer;910 if (customer.email) {11 await sendSubscriptionConfirmation(12 customer.email,13 "Pro Plan", // Get plan name from price ID14 "$29/month"15 );16 }17 break;1819 case "invoice.payment_succeeded":20 const invoice = event.data.object as Stripe.Invoice;21 const customer = await stripe.customers.retrieve(22 invoice.customer as string23 ) as Stripe.Customer;2425 if (customer.email && invoice.billing_reason === "subscription_create") {26 await sendWelcomeEmail(customer.email, customer.name);27 }28 break;2930 case "invoice.payment_failed":31 const failedInvoice = event.data.object as Stripe.Invoice;32 // Send payment failure email33 break;34 }35}
Deliverability
- • Verify your sending domain
- • Use clear, non-spammy subject lines
- • Include unsubscribe links
- • Monitor bounce rates
User Experience
- • Keep emails mobile-friendly
- • Use consistent branding
- • Provide clear calls-to-action
- • Handle errors gracefully
Emails not sending
Check your RESEND_API_KEY and FROM_EMAIL configuration. Verify domain in Resend dashboard.
Emails going to spam
Set up domain verification and DKIM records. Avoid spammy content and excessive links.
Magic links not working
Ensure NEXTAUTH_URL matches your domain and FROM_EMAIL is verified.
Email System Ready!
Your transactional email system is configured. Next, learn about the waitlist and admin panel features.