Email System
Multi-provider email system with customizable templates
Email System
The starter kit includes a flexible email system supporting multiple providers with a visual template editor.
Supported Providers
- ✅ Nodemailer (SMTP) - Default, works with any SMTP server
- ✅ Resend - Modern email API
- ✅ SendGrid - Popular email service
- ✅ Mailgun - Developer-friendly email API
Configuration
Choose Your Provider
Set in .env:
EMAIL_PROVIDER="nodemailer" # or "resend", "sendgrid", "mailgun"Nodemailer (SMTP)
Works with Gmail, Outlook, or any SMTP server:
EMAIL_PROVIDER="nodemailer"
EMAIL_HOST="smtp.gmail.com"
EMAIL_PORT="587"
EMAIL_USER="your-email@gmail.com"
EMAIL_PASS="your-app-password"
EMAIL_SECURE="false"
EMAIL_FROM="Your App <noreply@yourapp.com>"Gmail Setup:
- Enable 2FA on your Google account
- Generate App Password: myaccount.google.com/apppasswords
- Use app password in
EMAIL_PASS
Resend
Modern email API with great DX:
EMAIL_PROVIDER="resend"
RESEND_API_KEY="re_xxxxxxxxxxxx"
EMAIL_FROM="Your App <noreply@yourdomain.com>"Get API key: resend.com/api-keys
SendGrid
EMAIL_PROVIDER="sendgrid"
SENDGRID_API_KEY="SG.xxxxxxxxxxxx"
EMAIL_FROM="Your App <noreply@yourdomain.com>"Get API key: app.sendgrid.com/settings/api_keys
Mailgun
EMAIL_PROVIDER="mailgun"
MAILGUN_API_KEY="your-api-key"
MAILGUN_DOMAIN="mg.yourdomain.com"
MAILGUN_REGION="us" # or "eu"
EMAIL_FROM="Your App <noreply@mg.yourdomain.com>"Get credentials: app.mailgun.com
Email Templates
Template Types
The system includes pre-built templates:
- welcome - Welcome new users
- email-verification - Verify email address
- password-reset - Reset password link
- subscription-created - New subscription confirmation
- subscription-canceled - Cancellation confirmation
- invoice-paid - Payment receipt
Template Structure
{
type: "welcome",
subject: "Welcome to {{appName}}!",
htmlContent: "<h1>Welcome, {{userName}}!</h1>...",
textContent: "Welcome, {{userName}}!...",
variables: ["appName", "userName", "loginUrl"],
active: true
}Template Editor
Navigate to /dashboard/admin/email-templates to:
- View all templates
- Edit HTML and text versions
- Preview with sample data
- Test send emails
- Manage template variables
Monaco Editor
The template editor uses Monaco (VS Code editor):
- Syntax highlighting for HTML
- Auto-completion
- Error checking
- Split view (HTML + Preview)
- Variable suggestions
Template Variables
Use {{variableName}} in templates:
<h1>Hello, {{userName}}!</h1>
<p>Welcome to {{appName}}!</p>
<a href="{{verificationUrl}}">Verify your email</a>Common variables:
{{userName}}- User's name{{userEmail}}- User's email{{appName}}- Application name{{appUrl}}- Application URL{{verificationUrl}}- Email verification link{{resetUrl}}- Password reset link{{loginUrl}}- Sign in page URL
Sending Emails
Using the Email Service
import { sendEmail } from "@/lib/email";
// Send with template
await sendEmail({
to: "user@example.com",
template: "welcome",
variables: {
userName: "John Doe",
appName: "My App",
loginUrl: "https://myapp.com/sign-in",
},
});
// Send custom email
await sendEmail({
to: "user@example.com",
subject: "Custom Email",
html: "<h1>Hello!</h1><p>This is a custom email.</p>",
text: "Hello! This is a custom email.",
});Email Service Factory
The system automatically selects the configured provider:
// lib/email/index.ts
import { getEmailService } from "@/lib/email";
const emailService = getEmailService();
// Provider-agnostic sending
await emailService.send({
to: ["user@example.com"],
subject: "Test Email",
html: "<p>Hello!</p>",
});Transactional Emails
Emails are sent automatically for:
Authentication:
- Email verification after sign-up
- Password reset requests
- Email change confirmation
Subscriptions (if enabled):
- New subscription created
- Subscription canceled
- Payment failed
- Invoice paid
Template Examples
Welcome Email
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
}
.container {
max-width: 600px;
margin: 0 auto;
}
.button {
background: #0070f3;
color: white;
padding: 12px 24px;
text-decoration: none;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>Welcome to {{appName}}!</h1>
<p>Hi {{userName}},</p>
<p>Thank you for joining our platform. We're excited to have you!</p>
<p>
<a href="{{loginUrl}}" class="button"> Get Started </a>
</p>
<p>Best regards,<br />The {{appName}} Team</p>
</div>
</body>
</html>Password Reset
<!DOCTYPE html>
<html>
<body>
<div class="container">
<h1>Reset Your Password</h1>
<p>Hi {{userName}},</p>
<p>
You requested to reset your password. Click the button below to create a
new password:
</p>
<p>
<a href="{{resetUrl}}" class="button"> Reset Password </a>
</p>
<p>This link will expire in 1 hour.</p>
<p>If you didn't request this, you can safely ignore this email.</p>
</div>
</body>
</html>Advanced Features
Batch Sending
Send emails to multiple recipients:
const recipients = ["user1@example.com", "user2@example.com"];
for (const email of recipients) {
await sendEmail({
to: email,
template: "newsletter",
variables: {
/* ... */
},
});
// Rate limiting - wait between sends
await new Promise((resolve) => setTimeout(resolve, 100));
}Attachments
Send files with emails:
await sendEmail({
to: "user@example.com",
subject: "Invoice",
html: "<p>Your invoice is attached.</p>",
attachments: [
{
filename: "invoice.pdf",
path: "/path/to/invoice.pdf",
},
],
});CC and BCC
await sendEmail({
to: "user@example.com",
cc: ["manager@example.com"],
bcc: ["admin@example.com"],
subject: "Important Notice",
html: "<p>Notice content</p>",
});Custom Headers
await sendEmail({
to: "user@example.com",
subject: "Newsletter",
html: "<p>Newsletter content</p>",
headers: {
"X-Campaign-Id": "newsletter-2024-01",
"X-Priority": "3",
},
});Testing
Test Email Sending
In development, use test recipients:
// Test mode - don't actually send
if (process.env.NODE_ENV === "development") {
console.log("Would send email:", {
to,
subject,
html,
});
return { success: true, messageId: "test" };
}
// Production - actually send
const result = await emailService.send({ to, subject, html });Preview Emails
Use the template editor preview to see how emails look before sending.
Send Test Emails
From the template editor, send test emails to yourself to verify rendering and deliverability.
Monitoring
Check Email Logs
Most providers offer email logs:
- Resend: Dashboard > Emails
- SendGrid: Activity > Email Activity
- Mailgun: Logs tab
Delivery Status
Track email delivery status:
// Most providers return a message ID
const result = await sendEmail({
to: "user@example.com",
subject: "Test",
html: "<p>Test</p>",
});
console.log("Message ID:", result.messageId);
// Use this to track delivery in provider dashboardBounce Handling
Configure bounce webhooks in your email provider to handle bounced emails.
Troubleshooting
Emails not sending
- Check credentials - Verify API keys/SMTP credentials
- Check from address - Must be verified with provider
- Check rate limits - Provider may have rate limits
- Check spam filters - Test emails may go to spam
- Check logs - Look at console errors
Emails going to spam
- Verify your domain - Set up SPF, DKIM, DMARC
- Use verified from address - Don't use random addresses
- Good content - Avoid spam trigger words
- Warm up IP - New accounts may have lower reputation
Template variables not working
- Check variable names - Must match exactly
- Check template syntax - Use
{{variableName}} - Provide all variables - Missing variables show as empty
- Check for typos - Variable names are case-sensitive
Best Practices
- Always provide text version - For email clients that don't support HTML
- Keep templates simple - Complex HTML may not render correctly
- Test on multiple clients - Gmail, Outlook, Apple Mail, etc.
- Use responsive design - Mobile-friendly templates
- Include unsubscribe link - Required for marketing emails
- Respect rate limits - Don't send too many emails too fast
- Handle errors gracefully - Email sending can fail
- Log important emails - Keep audit trail for critical emails
Security
Prevent Email Injection
Always validate email addresses:
import { z } from "zod";
const emailSchema = z.string().email();
// Validate before sending
const validated = emailSchema.parse(userEmail);Rate Limiting
Limit email sending to prevent abuse:
import { checkRateLimit, RateLimitPresets } from "@/lib/rate-limit";
const rateLimit = checkRateLimit(
`email:${userId}`,
RateLimitPresets.EMAIL_SEND
);
if (!rateLimit.success) {
throw new Error("Too many emails sent");
}
await sendEmail({
/* ... */
});Sensitive Data
Don't include sensitive data in emails:
- ❌ Passwords
- ❌ API keys
- ❌ Credit card numbers
- ✅ Links to reset passwords
- ✅ Generic confirmations