Email Templates

Create beautiful, responsive email templates that reinforce your brand and deliver important messages with professional design and reliability.

Email Templates Overview

Professional email templates ensure consistent branding and excellent user experience across all your transactional and marketing communications.

📧 Template Types

  • • Welcome emails
  • • Magic link authentication
  • • Subscription confirmations
  • • Payment receipts
  • • Plan change notifications
  • • Waitlist communications

🎨 Design Features

  • • Responsive design
  • • Dark mode support
  • • Brand consistency
  • • Accessibility compliance
  • • Cross-client compatibility
  • • A/B testing ready

Template System

Base Email Template

Reusable base template with consistent branding:

src/lib/emailTemplates.ts
1// src/lib/emailTemplates.ts
2interface EmailTemplateProps {
3 title: string;
4 preheader?: string;
5 children: string;
6 actionUrl?: string;
7 actionText?: string;
8 footerText?: string;
9}
10
11export function generateEmailTemplate({
12 title,
13 preheader,
14 children,
15 actionUrl,
16 actionText,
17 footerText
18}: EmailTemplateProps): string {
19 return `
20 <!DOCTYPE html>
21 <html lang="en">
22 <head>
23 <meta charset="UTF-8">
24 <meta name="viewport" content="width=device-width, initial-scale=1.0">
25 <title>${title}</title>
26 ${preheader ? `<meta name="description" content="${preheader}">` : ''}
27 <!--[if mso]>
28 <noscript>
29 <xml>
30 <o:OfficeDocumentSettings>
31 <o:PixelsPerInch>96</o:PixelsPerInch>
32 </o:OfficeDocumentSettings>
33 </xml>
34 </noscript>
35 <![endif]-->
36 <style>
37 /* Reset styles */
38 body, table, td, p, a, li, blockquote {
39 -webkit-text-size-adjust: 100%;
40 -ms-text-size-adjust: 100%;
41 }
42
43 table, td {
44 mso-table-lspace: 0pt;
45 mso-table-rspace: 0pt;
46 }
47
48 img {
49 -ms-interpolation-mode: bicubic;
50 border: 0;
51 height: auto;
52 line-height: 100%;
53 outline: none;
54 text-decoration: none;
55 }
56
57 /* Mobile styles */
58 @media only screen and (max-width: 600px) {
59 .mobile-full-width {
60 width: 100% !important;
61 height: auto !important;
62 }
63
64 .mobile-center {
65 text-align: center !important;
66 }
67
68 .mobile-padding {
69 padding: 20px !important;
70 }
71 }
72
73 /* Dark mode support */
74 @media (prefers-color-scheme: dark) {
75 .email-container {
76 background-color: #1a1a1a !important;
77 }
78
79 .email-content {
80 background-color: #2a2a2a !important;
81 color: #ffffff !important;
82 }
83
84 .text-muted {
85 color: #a0a0a0 !important;
86 }
87 }
88 </style>
89 </head>
90 <body style="margin: 0; padding: 0; background-color: #f5f5f5; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;">
91 ${preheader ? `
92 <!-- Preheader text -->
93 <div style="display: none; font-size: 1px; color: #f5f5f5; line-height: 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden;">
94 ${preheader}
95 </div>
96 ` : ''}
97
98 <!-- Email container -->
99 <table role="presentation" class="email-container" width="100%" cellpadding="0" cellspacing="0" style="background-color: #f5f5f5;">
100 <tr>
101 <td align="center" style="padding: 40px 20px;">
102
103 <!-- Main content table -->
104 <table role="presentation" class="email-content mobile-full-width" width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);">
105
106 <!-- Header -->
107 <tr>
108 <td align="center" style="padding: 40px 40px 20px; border-bottom: 1px solid #e5e5e5;">
109 <img src="${process.env.COMPANY_URL}/logo.png" alt="${process.env.COMPANY_NAME}" width="120" height="40" style="display: block;">
110 </td>
111 </tr>
112
113 <!-- Title -->
114 <tr>
115 <td style="padding: 30px 40px 20px;">
116 <h1 style="margin: 0; font-size: 28px; font-weight: 600; color: #1a1a1a; text-align: center; line-height: 1.3;">
117 ${title}
118 </h1>
119 </td>
120 </tr>
121
122 <!-- Content -->
123 <tr>
124 <td class="mobile-padding" style="padding: 0 40px 30px;">
125 <div style="font-size: 16px; line-height: 1.6; color: #4a4a4a;">
126 ${children}
127 </div>
128 </td>
129 </tr>
130
131 ${actionUrl && actionText ? `
132 <!-- Action button -->
133 <tr>
134 <td align="center" style="padding: 0 40px 40px;">
135 <table role="presentation" cellpadding="0" cellspacing="0">
136 <tr>
137 <td style="background-color: #007bff; border-radius: 6px;">
138 <a href="${actionUrl}" style="display: inline-block; padding: 16px 32px; font-size: 16px; font-weight: 600; color: #ffffff; text-decoration: none;">
139 ${actionText}
140 </a>
141 </td>
142 </tr>
143 </table>
144 </td>
145 </tr>
146 ` : ''}
147
148 <!-- Footer -->
149 <tr>
150 <td style="padding: 30px 40px; background-color: #f8f9fa; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;">
151 <div style="text-align: center;">
152 <p class="text-muted" style="margin: 0; font-size: 14px; color: #6c757d;">
153 ${footerText || `
154 This email was sent by ${process.env.COMPANY_NAME}.
155 If you have any questions, contact us at ${process.env.SUPPORT_EMAIL}.
156 `}
157 </p>
158 ${process.env.COMPANY_ADDRESS ? `
159 <p class="text-muted" style="margin: 10px 0 0; font-size: 12px; color: #6c757d;">
160 ${process.env.COMPANY_ADDRESS}
161 </p>
162 ` : ''}
163 </div>
164 </td>
165 </tr>
166
167 </table>
168
169 </td>
170 </tr>
171 </table>
172 </body>
173 </html>
174 `;
175}
Welcome Email Template

Branded welcome email for new users:

Welcome email templates
1export function generateWelcomeEmail(userName?: string): string {
2 const name = userName || "there";
3
4 const content = `
5 <p style="margin: 0 0 20px;">Hi ${name},</p>
6
7 <p style="margin: 0 0 20px;">
8 Welcome to ${process.env.COMPANY_NAME}! We&apos;re thrilled to have you join our community
9 of forward-thinking professionals.
10 </p>
11
12 <p style="margin: 0 0 20px;">
13 Your account is now active and ready to use. Here&apos;s what you can do next:
14 </p>
15
16 <ul style="margin: 0 0 20px; padding-left: 20px;">
17 <li style="margin-bottom: 8px;">Complete your profile setup</li>
18 <li style="margin-bottom: 8px;">Explore our getting started guide</li>
19 <li style="margin-bottom: 8px;">Connect with our community</li>
20 <li>Start your first project</li>
21 </ul>
22
23 <div style="background-color: #f8f9fa; border-left: 4px solid #007bff; padding: 16px; margin: 20px 0; border-radius: 4px;">
24 <p style="margin: 0; font-weight: 600; color: #007bff;">💡 Pro Tip</p>
25 <p style="margin: 8px 0 0; font-size: 14px;">
26 Check out our <a href="${process.env.COMPANY_URL}/docs" style="color: #007bff; text-decoration: none;">documentation</a>
27 to learn about advanced features and best practices.
28 </p>
29 </div>
30
31 <p style="margin: 20px 0 0;">
32 If you have any questions or need assistance, don&apos;t hesitate to reach out.
33 We&apos;re here to help you succeed!
34 </p>
35 `;
36
37 return generateEmailTemplate({
38 title: `Welcome to ${process.env.COMPANY_NAME}! 🎉`,
39 preheader: "Your account is ready - let&apos;s get started!",
40 children: content,
41 actionUrl: `${process.env.COMPANY_URL}/account`,
42 actionText: "Get Started",
43 });
44}
45
46export function generateSubscriptionWelcomeEmail(planName: string): string {
47 const content = `
48 <p style="margin: 0 0 20px;">
49 Thank you for upgrading to ${planName}! Your subscription is now active
50 and you have access to all premium features.
51 </p>
52
53 <div style="background-color: #e8f5e8; border: 1px solid #4caf50; border-radius: 8px; padding: 20px; margin: 20px 0;">
54 <h3 style="margin: 0 0 12px; color: #2e7d32; font-size: 18px;">
55 🚀 What&apos;s included in ${planName}:
56 </h3>
57 <ul style="margin: 0; padding-left: 20px; color: #2e7d32;">
58 <li style="margin-bottom: 8px;">Unlimited projects and workflows</li>
59 <li style="margin-bottom: 8px;">Advanced analytics and reporting</li>
60 <li style="margin-bottom: 8px;">Priority customer support</li>
61 <li style="margin-bottom: 8px;">Team collaboration features</li>
62 <li>API access and integrations</li>
63 </ul>
64 </div>
65
66 <p style="margin: 20px 0;">
67 Ready to explore your new capabilities? Your account has been automatically
68 upgraded and all premium features are now available.
69 </p>
70 `;
71
72 return generateEmailTemplate({
73 title: "Welcome to Premium! 🎉",
74 preheader: `Your ${planName} subscription is now active`,
75 children: content,
76 actionUrl: `${process.env.COMPANY_URL}/account`,
77 actionText: "Explore Premium Features",
78 });
79}

Authentication Templates

Magic Link Email

Secure magic link authentication emails:

Authentication email templates
1export function generateMagicLinkEmail(magicLinkUrl: string): string {
2 const content = `
3 <p style="margin: 0 0 20px;">
4 Click the button below to securely sign in to your ${process.env.COMPANY_NAME} account.
5 This link will expire in 24 hours for your security.
6 </p>
7
8 <div style="background-color: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 16px; margin: 20px 0;">
9 <p style="margin: 0; font-size: 14px; color: #856404;">
10 🔒 <strong>Security Notice:</strong> This link will only work once and expires in 24 hours.
11 If you didn&apos;t request this, you can safely ignore this email.
12 </p>
13 </div>
14
15 <p style="margin: 20px 0;">
16 If the button doesn&apos;t work, you can copy and paste this link into your browser:
17 </p>
18
19 <p style="margin: 0; padding: 12px; background-color: #f8f9fa; border-radius: 4px; font-family: monospace; font-size: 14px; word-break: break-all;">
20 ${magicLinkUrl}
21 </p>
22 `;
23
24 return generateEmailTemplate({
25 title: "Sign in to your account",
26 preheader: "Click to securely access your account",
27 children: content,
28 actionUrl: magicLinkUrl,
29 actionText: "Sign In Securely",
30 footerText: `
31 This sign-in link was requested for your ${process.env.COMPANY_NAME} account.
32 If you didn&apos;t request this, please ignore this email.
33 `,
34 });
35}
36
37export function generatePasswordResetEmail(resetUrl: string): string {
38 const content = `
39 <p style="margin: 0 0 20px;">
40 We received a request to reset the password for your ${process.env.COMPANY_NAME} account.
41 Click the button below to create a new password.
42 </p>
43
44 <div style="background-color: #f8d7da; border: 1px solid #dc3545; border-radius: 8px; padding: 16px; margin: 20px 0;">
45 <p style="margin: 0; font-size: 14px; color: #721c24;">
46 ⚠️ <strong>Important:</strong> This reset link expires in 1 hour. If you didn&apos;t
47 request a password reset, please contact our support team immediately.
48 </p>
49 </div>
50
51 <p style="margin: 20px 0;">
52 For your security, this link will only work once and expires in 1 hour.
53 </p>
54 `;
55
56 return generateEmailTemplate({
57 title: "Reset your password",
58 preheader: "Click to reset your account password",
59 children: content,
60 actionUrl: resetUrl,
61 actionText: "Reset Password",
62 footerText: `
63 This password reset was requested for your ${process.env.COMPANY_NAME} account.
64 If you didn&apos;t request this, please contact support immediately.
65 `,
66 });
67}

Billing & Subscription Templates

Payment Confirmation

Professional payment receipt and confirmation emails:

Billing email templates
1export function generatePaymentReceiptEmail(paymentDetails: {
2 amount: string;
3 planName: string;
4 invoiceId: string;
5 paymentDate: string;
6 nextBillingDate: string;
7}): string {
8 const { amount, planName, invoiceId, paymentDate, nextBillingDate } = paymentDetails;
9
10 const content = `
11 <p style="margin: 0 0 20px;">
12 Thank you! Your payment has been successfully processed.
13 </p>
14
15 <!-- Receipt details -->
16 <table style="width: 100%; border-collapse: collapse; margin: 20px 0; background-color: #f8f9fa; border-radius: 8px;">
17 <tr>
18 <td style="padding: 20px;">
19 <h3 style="margin: 0 0 16px; color: #1a1a1a; font-size: 18px;">Payment Details</h3>
20
21 <table style="width: 100%; border-collapse: collapse;">
22 <tr>
23 <td style="padding: 8px 0; font-weight: 600; width: 40%;">Plan:</td>
24 <td style="padding: 8px 0;">${planName}</td>
25 </tr>
26 <tr>
27 <td style="padding: 8px 0; font-weight: 600;">Amount:</td>
28 <td style="padding: 8px 0; font-size: 18px; font-weight: 600; color: #28a745;">${amount}</td>
29 </tr>
30 <tr>
31 <td style="padding: 8px 0; font-weight: 600;">Payment Date:</td>
32 <td style="padding: 8px 0;">${paymentDate}</td>
33 </tr>
34 <tr>
35 <td style="padding: 8px 0; font-weight: 600;">Invoice ID:</td>
36 <td style="padding: 8px 0; font-family: monospace; font-size: 14px;">${invoiceId}</td>
37 </tr>
38 <tr>
39 <td style="padding: 8px 0; font-weight: 600;">Next Billing:</td>
40 <td style="padding: 8px 0;">${nextBillingDate}</td>
41 </tr>
42 </table>
43 </td>
44 </tr>
45 </table>
46
47 <p style="margin: 20px 0;">
48 Your subscription is active and all premium features are available in your account.
49 </p>
50
51 <p style="margin: 20px 0;">
52 Questions about your bill? You can view your complete billing history
53 and download invoices from your account settings.
54 </p>
55 `;
56
57 return generateEmailTemplate({
58 title: "Payment Confirmation",
59 preheader: `Payment of ${amount} received - thank you!`,
60 children: content,
61 actionUrl: `${process.env.COMPANY_URL}/account`,
62 actionText: "View Account",
63 });
64}
65
66export function generateSubscriptionCanceledEmail(endDate: string): string {
67 const content = `
68 <p style="margin: 0 0 20px;">
69 We&apos;re sorry to see you go! Your subscription has been canceled as requested.
70 </p>
71
72 <div style="background-color: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 16px; margin: 20px 0;">
73 <p style="margin: 0; font-size: 14px; color: #856404;">
74 📅 <strong>Important:</strong> Your account will remain active until ${endDate}.
75 You&apos;ll continue to have access to all premium features until then.
76 </p>
77 </div>
78
79 <p style="margin: 20px 0;">
80 What happens next:
81 </p>
82
83 <ul style="margin: 0 0 20px; padding-left: 20px;">
84 <li style="margin-bottom: 8px;">Your subscription remains active until ${endDate}</li>
85 <li style="margin-bottom: 8px;">No further charges will be made</li>
86 <li style="margin-bottom: 8px;">You can reactivate anytime before ${endDate}</li>
87 <li>After ${endDate}, your account will switch to the free plan</li>
88 </ul>
89
90 <p style="margin: 20px 0;">
91 Changed your mind? You can easily reactivate your subscription from your account settings.
92 </p>
93 `;
94
95 return generateEmailTemplate({
96 title: "Subscription Canceled",
97 preheader: `Access continues until ${endDate}`,
98 children: content,
99 actionUrl: `${process.env.COMPANY_URL}/account`,
100 actionText: "Manage Subscription",
101 });
102}

Marketing Templates

Product Updates & Announcements

Engaging templates for feature announcements and updates:

Marketing email templates
1export function generateFeatureAnnouncementEmail(feature: {
2 name: string;
3 description: string;
4 benefits: string[];
5 imageUrl?: string;
6 ctaUrl?: string;
7}): string {
8 const { name, description, benefits, imageUrl, ctaUrl } = feature;
9
10 const content = `
11 <p style="margin: 0 0 20px;">
12 We&apos;re excited to share something new that will make your workflow even better!
13 </p>
14
15 ${imageUrl ? `
16 <div style="text-align: center; margin: 30px 0;">
17 <img src="${imageUrl}" alt="${name}" style="max-width: 100%; height: auto; border-radius: 8px;">
18 </div>
19 ` : ''}
20
21 <h2 style="margin: 30px 0 16px; color: #1a1a1a; font-size: 24px;">
22 🎉 Introducing ${name}
23 </h2>
24
25 <p style="margin: 0 0 20px; font-size: 18px; line-height: 1.6;">
26 ${description}
27 </p>
28
29 <h3 style="margin: 30px 0 16px; color: #1a1a1a; font-size: 20px;">
30 What this means for you:
31 </h3>
32
33 <ul style="margin: 0 0 30px; padding-left: 20px;">
34 ${benefits.map(benefit => `
35 <li style="margin-bottom: 12px; line-height: 1.6;">
36 <strong>${benefit.split(':')[0]}:</strong> ${benefit.split(':').slice(1).join(':')}
37 </li>
38 `).join('')}
39 </ul>
40
41 <div style="background-color: #e3f2fd; border-left: 4px solid #2196f3; padding: 16px; margin: 20px 0; border-radius: 4px;">
42 <p style="margin: 0; font-weight: 600; color: #1976d2;">💡 Available Now</p>
43 <p style="margin: 8px 0 0; font-size: 14px; color: #1976d2;">
44 This feature is already live in your account. Log in to start using it today!
45 </p>
46 </div>
47 `;
48
49 return generateEmailTemplate({
50 title: `New Feature: ${name}`,
51 preheader: `${description.substring(0, 100)}...`,
52 children: content,
53 actionUrl: ctaUrl || `${process.env.COMPANY_URL}/account`,
54 actionText: "Try It Now",
55 });
56}
57
58export function generateNewsletterTemplate(newsletter: {
59 title: string;
60 articles: Array<{
61 title: string;
62 excerpt: string;
63 url: string;
64 imageUrl?: string;
65 }>;
66 personalNote?: string;
67}): string {
68 const { title, articles, personalNote } = newsletter;
69
70 const content = `
71 ${personalNote ? `
72 <div style="background-color: #f8f9fa; border-radius: 8px; padding: 20px; margin: 0 0 30px;">
73 <p style="margin: 0; font-style: italic; color: #6c757d;">
74 "${personalNote}"
75 </p>
76 <p style="margin: 12px 0 0; font-size: 14px; font-weight: 600; color: #6c757d;">
77${process.env.COMPANY_NAME} Team
78 </p>
79 </div>
80 ` : ''}
81
82 <h2 style="margin: 0 0 20px; color: #1a1a1a; font-size: 24px;">
83 This Week&apos;s Highlights
84 </h2>
85
86 ${articles.map((article, index) => `
87 <div style="margin: 30px 0; padding: 20px 0; ${index > 0 ? 'border-top: 1px solid #e5e5e5;' : ''}">
88 ${article.imageUrl ? `
89 <img src="${article.imageUrl}" alt="${article.title}" style="width: 100%; height: 200px; object-fit: cover; border-radius: 8px; margin-bottom: 16px;">
90 ` : ''}
91
92 <h3 style="margin: 0 0 12px; color: #1a1a1a; font-size: 20px; line-height: 1.4;">
93 <a href="${article.url}" style="color: #1a1a1a; text-decoration: none;">
94 ${article.title}
95 </a>
96 </h3>
97
98 <p style="margin: 0 0 16px; color: #4a4a4a; line-height: 1.6;">
99 ${article.excerpt}
100 </p>
101
102 <a href="${article.url}" style="color: #007bff; text-decoration: none; font-weight: 600;">
103 Read more →
104 </a>
105 </div>
106 `).join('')}
107 `;
108
109 return generateEmailTemplate({
110 title,
111 preheader: "Your weekly dose of insights and updates",
112 children: content,
113 footerText: `
114 You&apos;re receiving this newsletter because you&apos;re a valued member of ${process.env.COMPANY_NAME}.
115 <a href="${process.env.COMPANY_URL}/unsubscribe" style="color: #007bff;">Unsubscribe</a>
116 | <a href="${process.env.COMPANY_URL}/preferences" style="color: #007bff;">Email Preferences</a>
117 `,
118 });
119}

Testing & Optimization

Email Testing

Test your email templates across different clients and devices:

Email testing utilities
1// src/lib/emailTesting.ts
2import { generateEmailTemplate } from "./emailTemplates";
3
4export async function sendTestEmail(templateName: string, recipientEmail: string) {
5 let htmlContent: string;
6
7 switch (templateName) {
8 case 'welcome':
9 htmlContent = generateWelcomeEmail("Test User");
10 break;
11 case 'magic-link':
12 htmlContent = generateMagicLinkEmail("https://example.com/test-link");
13 break;
14 case 'payment-receipt':
15 htmlContent = generatePaymentReceiptEmail({
16 amount: "$29.00",
17 planName: "Pro Monthly",
18 invoiceId: "inv_test123",
19 paymentDate: "March 15, 2024",
20 nextBillingDate: "April 15, 2024"
21 });
22 break;
23 default:
24 throw new Error(`Unknown template: ${templateName}`);
25 }
26
27 // Send via your email service
28 const { data, error } = await resend.emails.send({
29 from: `Test <${process.env.FROM_EMAIL}>`,
30 to: [recipientEmail],
31 subject: `[TEST] ${templateName} Template`,
32 html: htmlContent,
33 });
34
35 if (error) {
36 throw new Error(`Test email failed: ${error.message}`);
37 }
38
39 return data;
40}
41
42// Email preview for development
43export function generateEmailPreview(templateName: string): string {
44 // Generate HTML that can be viewed in browser
45 const htmlContent = generateWelcomeEmail("Preview User");
46
47 return `
48 <!DOCTYPE html>
49 <html>
50 <head>
51 <title>Email Preview: ${templateName}</title>
52 <style>
53 body { margin: 0; padding: 20px; background: #f0f0f0; }
54 .preview-controls {
55 background: white;
56 padding: 20px;
57 border-radius: 8px;
58 margin-bottom: 20px;
59 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
60 }
61 .email-frame {
62 border: 1px solid #ddd;
63 border-radius: 8px;
64 overflow: hidden;
65 background: white;
66 }
67 </style>
68 </head>
69 <body>
70 <div class="preview-controls">
71 <h2>Email Preview: ${templateName}</h2>
72 <p>This is how your email will look to recipients.</p>
73 <button onclick="window.print()">Print</button>
74 <button onclick="toggleDarkMode()">Toggle Dark Mode</button>
75 </div>
76
77 <div class="email-frame">
78 ${htmlContent}
79 </div>
80
81 <script>
82 function toggleDarkMode() {
83 document.body.style.filter = document.body.style.filter ? '' : 'invert(1) hue-rotate(180deg)';
84 }
85 </script>
86 </body>
87 </html>
88 `;
89}
A/B Testing Framework

Test different email variations to optimize engagement:

A/B testing framework
1export interface EmailVariant {
2 id: string;
3 name: string;
4 subject: string;
5 template: string;
6 weight: number; // Percentage of traffic (0-100)
7}
8
9export class EmailABTest {
10 private variants: EmailVariant[];
11 private testId: string;
12
13 constructor(testId: string, variants: EmailVariant[]) {
14 this.testId = testId;
15 this.variants = variants;
16
17 // Ensure weights add up to 100
18 const totalWeight = variants.reduce((sum, v) => sum + v.weight, 0);
19 if (totalWeight !== 100) {
20 throw new Error(`Variant weights must sum to 100, got ${totalWeight}`);
21 }
22 }
23
24 // Select variant based on user ID for consistent assignment
25 selectVariant(userId: string): EmailVariant {
26 const hash = this.hashString(`${this.testId}-${userId}`);
27 const bucket = hash % 100;
28
29 let currentWeight = 0;
30 for (const variant of this.variants) {
31 currentWeight += variant.weight;
32 if (bucket < currentWeight) {
33 return variant;
34 }
35 }
36
37 return this.variants[0]; // Fallback
38 }
39
40 private hashString(str: string): number {
41 let hash = 0;
42 for (let i = 0; i < str.length; i++) {
43 const char = str.charCodeAt(i);
44 hash = ((hash << 5) - hash) + char;
45 hash = hash & hash; // Convert to 32-bit integer
46 }
47 return Math.abs(hash);
48 }
49
50 // Track email metrics
51 async trackEvent(userId: string, variantId: string, event: 'sent' | 'opened' | 'clicked') {
52 // Store in your analytics system
53 await fetch('/api/analytics/email', {
54 method: 'POST',
55 headers: { 'Content-Type': 'application/json' },
56 body: JSON.stringify({
57 testId: this.testId,
58 userId,
59 variantId,
60 event,
61 timestamp: new Date().toISOString(),
62 }),
63 });
64 }
65}
66
67// Usage example
68export async function sendWelcomeEmailWithTest(userId: string, userEmail: string) {
69 const abTest = new EmailABTest('welcome-subject-test', [
70 {
71 id: 'control',
72 name: 'Control - Standard Welcome',
73 subject: 'Welcome to RankThis!',
74 template: 'welcome-standard',
75 weight: 50,
76 },
77 {
78 id: 'variant-a',
79 name: 'Variant A - Benefit Focused',
80 subject: 'Your RankThis account is ready - start building!',
81 template: 'welcome-benefits',
82 weight: 50,
83 },
84 ]);
85
86 const selectedVariant = abTest.selectVariant(userId);
87
88 // Send email with selected variant
89 const result = await resend.emails.send({
90 from: `RankThis <${process.env.FROM_EMAIL}>`,
91 to: [userEmail],
92 subject: selectedVariant.subject,
93 html: generateWelcomeEmail(userEmail), // Use appropriate template
94 });
95
96 // Track the send event
97 await abTest.trackEvent(userId, selectedVariant.id, 'sent');
98
99 return result;
100}
💡 Email Template Best Practices

Design & Accessibility

  • • Use web-safe fonts and fallbacks
  • • Include alt text for all images
  • • Ensure good color contrast ratios
  • • Test with images disabled
  • • Use semantic HTML structure
  • • Support dark mode preferences

Deliverability

  • • Keep subject lines under 50 characters
  • • Include preheader text
  • • Maintain good text-to-image ratio
  • • Avoid spam trigger words
  • • Include unsubscribe links
  • • Use consistent from addresses

🎉 Key Components Complete!

Your email templates are professional and conversion-optimized. You've completed all Key Components! Next: Security & Deployment guides.