Email System

Learn how to configure Resend for transactional emails, customize email templates, and ensure reliable delivery.

Email System Overview

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

Create Resend Account
  1. 1. Sign up at resend.com
  2. 2. Verify your email address
  3. 3. Create an API key in the dashboard
  4. 4. Set up domain verification (recommended)
  5. 5. Configure your sending domain

📧 Domain Setup

Verify your domain in Resend to improve deliverability and remove the "via resend.dev" label from emails.

Environment Configuration

Add your Resend configuration to environment variables:

.env
# Resend Email Configuration
RESEND_API_KEY="re_..." # Get from Resend dashboard
FROM_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

Email Service Module

The email service handles all transactional email sending:

src/server/lib/emailService.ts
1import { Resend } from "resend";
2
3const resend = new Resend(process.env.RESEND_API_KEY!);
4
5interface EmailOptions {
6 to: string;
7 subject: string;
8 html: string;
9 from?: string;
10}
11
12export 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 });
20
21 if (error) {
22 console.error("Email send error:", error);
23 throw new Error(`Failed to send email: ${error.message}`);
24 }
25
26 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}
33
34// Welcome email
35export async function sendWelcomeEmail(userEmail: string, userName?: string) {
36 const html = generateWelcomeEmailHTML(userName);
37
38 return sendEmail({
39 to: userEmail,
40 subject: `Welcome to ${process.env.COMPANY_NAME}!`,
41 html,
42 });
43}
44
45// Subscription confirmation
46export async function sendSubscriptionConfirmation(
47 userEmail: string,
48 planName: string,
49 amount: string
50) {
51 const html = generateSubscriptionEmailHTML(planName, amount);
52
53 return sendEmail({
54 to: userEmail,
55 subject: "Subscription Confirmed",
56 html,
57 });
58}

Email Templates

Welcome Email Template

Branded welcome email sent to new users:

Welcome email template
1export function generateWelcomeEmailHTML(userName?: string) {
2 const name = userName ?? "there";
3
4 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>
25
26 <!-- 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>
32
33 <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>
36
37 <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>
40
41 <!-- 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 Dashboard
48 </a>
49 </td>
50 </tr>
51 </table>
52 </td>
53 </tr>
54
55 <!-- 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 at
60 <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}
Subscription Email Template

Payment confirmation email for successful subscriptions:

Subscription email template
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>
26
27 <!-- 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>
33
34 <!-- 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>
46
47 <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>
50
51 <!-- 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 Subscription
58 </a>
59 </td>
60 </tr>
61 </table>
62 </td>
63 </tr>
64
65 <!-- 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 at
70 <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

Authentication Emails

Magic link emails are automatically sent by NextAuth.js:

Authentication email setup
1// NextAuth configuration with Resend
2import Resend from "next-auth/providers/resend";
3
4export 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 config
12});
13
14// 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})
Webhook Email Triggers

Send emails based on Stripe webhook events:

Webhook email triggers
1// In your Stripe webhook handler
2export 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 string
8 ) as Stripe.Customer;
9
10 if (customer.email) {
11 await sendSubscriptionConfirmation(
12 customer.email,
13 "Pro Plan", // Get plan name from price ID
14 "$29/month"
15 );
16 }
17 break;
18
19 case "invoice.payment_succeeded":
20 const invoice = event.data.object as Stripe.Invoice;
21 const customer = await stripe.customers.retrieve(
22 invoice.customer as string
23 ) as Stripe.Customer;
24
25 if (customer.email && invoice.billing_reason === "subscription_create") {
26 await sendWelcomeEmail(customer.email, customer.name);
27 }
28 break;
29
30 case "invoice.payment_failed":
31 const failedInvoice = event.data.object as Stripe.Invoice;
32 // Send payment failure email
33 break;
34 }
35}
💡 Email Best Practices

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
🔧 Common Issues

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.