Email Integration
LaunchKit includes comprehensive email functionality using Resend for reliable email delivery. This guide covers the complete setup and implementation based on the actual LaunchKit codebase.
Quick Start
Section titled “Quick Start”LaunchKit comes pre-configured with Resend. You just need to:
- Get your Resend API key
- Configure your domain
- Set up environment variables
- Start sending emails
Resend Setup
Section titled “Resend Setup”1. Create Resend Account
Section titled “1. Create Resend Account”- Sign up at resend.com (free tier includes 3,000 emails/month)
- Verify your domain or use Resend’s testing domain (
[email protected]
) - Generate an API key from the API Keys section
2. Environment Configuration
Section titled “2. Environment Configuration”Add your Resend API key to your environment variables:
RESEND_API_KEY=re_your_api_key_here
3. Domain Verification (Production)
Section titled “3. Domain Verification (Production)”For production, verify your domain in Resend dashboard:
- Add your domain in Resend dashboard
- Add DNS records (SPF, DKIM, DMARC)
- Wait for verification (usually takes a few minutes)
Required DNS Records:
# SPF RecordTXT @ "v=spf1 include:_spf.resend.com ~all"
# DKIM RecordTXT resend._domainkey "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC..."
# DMARC RecordTXT _dmarc "v=DMARC1; p=quarantine; rua=mailto:[email protected]"
LaunchKit Email Configuration
Section titled “LaunchKit Email Configuration”Config Setup
Section titled “Config Setup”LaunchKit uses a centralized config for email settings. Update your config.ts
:
const config = { // ... other config resend: { // Email 'From' field for system emails (magic links, password resets) // Email 'From' field for personal emails (updates, announcements) // Support email for customer inquiries },};
export default config;
Resend Service Implementation
Section titled “Resend Service Implementation”LaunchKit includes a pre-built email service at libs/resend.ts
:
import { Resend } from 'resend';import config from '@/config';
if (!process.env.RESEND_API_KEY) { throw new Error('RESEND_API_KEY is not set');}
const resend = new Resend(process.env.RESEND_API_KEY);
export const sendEmail = async ({ to, subject, text, html, replyTo,}: { to: string | string[]; subject: string; text: string; html: string; replyTo?: string | string[];}) => { const { data, error } = await resend.emails.send({ from: config.resend.fromAdmin, to, subject, text, html, ...(replyTo && { replyTo }), });
if (error) { console.error('Error sending email:', error.message); throw error; }
return data;};
React Email Templates
Section titled “React Email Templates”Installation
Section titled “Installation”Install React Email for beautiful, component-based templates:
npm install @react-email/components @react-email/rendernpm install react-email -D
pnpm add @react-email/components @react-email/renderpnpm add react-email -D
yarn add @react-email/components @react-email/renderyarn add react-email -D
bun add @react-email/components @react-email/renderbun add react-email -D
Basic Email Template
Section titled “Basic Email Template”Create your first email template:
import { Body, Button, Container, Head, Heading, Html, Link, Preview, Section, Text,} from '@react-email/components';import config from '@/config';
interface WelcomeEmailProps { name: string; dashboardUrl: string;}
export default function WelcomeEmail({ name = 'there', dashboardUrl = `${config.domainName}/dashboard`,}: WelcomeEmailProps) { return ( <Html> <Head /> <Preview>Welcome to {config.appName}! 🚀</Preview> <Body style={main}> <Container style={container}> <Heading style={h1}>Welcome to {config.appName}!</Heading>
<Text style={text}>Hi {name},</Text>
<Text style={text}> Welcome to {config.appName}! We're excited to have you on board. Your account is now ready, and you can start exploring all the features. </Text>
<Section style={buttonContainer}> <Button style={button} href={dashboardUrl}> Get Started </Button> </Section>
<Text style={text}> If you have any questions, feel free to reach out to our support team at {config.resend.supportEmail}. </Text>
<Text style={footer}> Best regards, <br /> The {config.appName} Team </Text> </Container> </Body> </Html> );}
// Stylesconst main = { backgroundColor: '#ffffff', fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',};
const container = { margin: '0 auto', padding: '20px 0 48px', maxWidth: '560px',};
const h1 = { color: '#333', fontSize: '24px', fontWeight: 'bold', paddingTop: '32px', paddingBottom: '32px',};
const text = { color: '#333', fontSize: '16px', lineHeight: '26px', paddingBottom: '16px',};
const buttonContainer = { textAlign: 'center' as const, padding: '32px 0',};
const button = { backgroundColor: '#000', borderRadius: '6px', color: '#fff', fontSize: '16px', fontWeight: 'bold', textDecoration: 'none', textAlign: 'center' as const, display: 'inline-block', padding: '12px 32px',};
const footer = { color: '#666', fontSize: '14px', paddingTop: '32px',};
Magic Link Email Template
Section titled “Magic Link Email Template”import { Body, Button, Container, Head, Heading, Html, Link, Preview, Section, Text,} from '@react-email/components';import config from '@/config';
interface MagicLinkEmailProps { magicLink: string; email: string;}
export default function MagicLinkEmail({ magicLink, email,}: MagicLinkEmailProps) { return ( <Html> <Head /> <Preview>Your magic link for {config.appName}</Preview> <Body style={main}> <Container style={container}> <Heading style={h1}>Sign in to {config.appName}</Heading>
<Text style={text}> Click the button below to securely sign in to your account: </Text>
<Section style={buttonContainer}> <Button style={button} href={magicLink}> Sign In to {config.appName} </Button> </Section>
<Text style={text}> Or copy and paste this URL into your browser: </Text>
<Link href={magicLink} style={link}> {magicLink} </Link>
<Text style={warning}> This link will expire in 24 hours and can only be used once. If you didn't request this email, you can safely ignore it. </Text> </Container> </Body> </Html> );}
// Styles (reuse from WelcomeEmail and add specific ones)const warning = { color: '#666', fontSize: '14px', lineHeight: '24px', paddingTop: '24px', borderTop: '1px solid #eee', marginTop: '32px',};
const link = { color: '#2563eb', textDecoration: 'underline', fontSize: '14px', wordBreak: 'break-all' as const,};
Email Sending Functions
Section titled “Email Sending Functions”Enhanced Email Service
Section titled “Enhanced Email Service”Create a comprehensive email service:
import { render } from '@react-email/render';import { sendEmail } from '@/libs/resend';import WelcomeEmail from '@/emails/WelcomeEmail';import MagicLinkEmail from '@/emails/MagicLinkEmail';import config from '@/config';
// Welcome emailexport async function sendWelcomeEmail(to: string, name: string) { const html = render( WelcomeEmail({ name, dashboardUrl: `${config.domainName}/dashboard`, }) );
const text = `Welcome to ${config.appName}! Hi ${name}, welcome to ${config.appName}! Visit ${config.domainName}/dashboard to get started.`;
return sendEmail({ to, subject: `Welcome to ${config.appName}! 🚀`, html, text, });}
// Magic link emailexport async function sendMagicLinkEmail(to: string, magicLink: string) { const html = render(MagicLinkEmail({ magicLink, email: to }));
const text = `Sign in to ${config.appName}: ${magicLink}`;
return sendEmail({ to, subject: `Sign in to ${config.appName}`, html, text, replyTo: config.resend.supportEmail, });}
// Lead notification emailexport async function sendLeadNotification(email: string) { const html = ` <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;"> <h2>New Lead Signup! 🎉</h2> <p><strong>Email:</strong> ${email}</p> <p><strong>Time:</strong> ${new Date().toLocaleString()}</p> <p><strong>Source:</strong> Landing page signup</p> </div> `;
const text = `New lead signup: ${email} at ${new Date().toLocaleString()}`;
return sendEmail({ to: config.resend.fromAdmin, subject: `New Lead: ${email}`, html, text, });}
// Lead welcome emailexport async function sendLeadWelcomeEmail(to: string) { const html = ` <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;"> <h2>Thanks for your interest in ${config.appName}! 🚀</h2> <p>Hi there!</p> <p>Thanks for signing up for updates about ${config.appName}. We're working hard to build something amazing, and we'll keep you in the loop as we make progress.</p> <p>You'll be among the first to know when we launch!</p> <p>Best regards,<br>The ${config.appName} Team</p> <hr style="margin: 32px 0; border: none; border-top: 1px solid #eee;"> <p style="color: #666; font-size: 14px;"> You received this email because you signed up for updates at ${config.domainName} </p> </div> `;
const text = `Thanks for your interest in ${config.appName}! We'll keep you updated on our progress.`;
return sendEmail({ to, subject: `Thanks for your interest in ${config.appName}!`, html, text, });}
API Integration
Section titled “API Integration”Enhanced Lead API Route
Section titled “Enhanced Lead API Route”Update your lead API route to send emails:
import { NextResponse, NextRequest } from 'next/server';import { createClient } from '@/libs/supabase/server';import { sendLeadNotification, sendLeadWelcomeEmail,} from '@/libs/email-service';
export async function POST(req: NextRequest) { const body = await req.json();
if (!body.email) { return NextResponse.json({ error: 'Email is required' }, { status: 400 }); }
try { // Save lead to database const supabase = createClient(); const { data, error } = await supabase .from('leads') .insert({ email: body.email }) .select() .single();
if (error) { console.error('Database error:', error); return NextResponse.json( { error: 'Failed to save lead' }, { status: 500 } ); }
// Send notification to admin await sendLeadNotification(body.email);
// Send welcome email to lead await sendLeadWelcomeEmail(body.email);
return NextResponse.json({ success: true, lead: data }); } catch (e) { console.error('Email sending error:', e); return NextResponse.json({ error: e.message }, { status: 500 }); }}
Email Testing API Route
Section titled “Email Testing API Route”Create a testing route for development:
import { NextRequest, NextResponse } from 'next/server';import { sendWelcomeEmail, sendMagicLinkEmail } from '@/libs/email-service';
export async function POST(request: NextRequest) { // Only allow in development if (process.env.NODE_ENV !== 'development') { return NextResponse.json( { error: 'Not available in production' }, { status: 403 } ); }
const { email, type, name } = await request.json();
if (!email) { return NextResponse.json({ error: 'Email is required' }, { status: 400 }); }
try { switch (type) { case 'welcome': await sendWelcomeEmail(email, name || 'Test User'); break; case 'magic-link': await sendMagicLinkEmail( email, 'https://example.com/auth/callback?token=test' ); break; default: return NextResponse.json( { error: 'Invalid email type. Use: welcome, magic-link' }, { status: 400 } ); }
return NextResponse.json({ success: true, message: `${type} email sent to ${email}`, }); } catch (error) { console.error('Email test error:', error); return NextResponse.json( { error: 'Failed to send test email' }, { status: 500 } ); }}
Development Workflow
Section titled “Development Workflow”1. Email Development Server
Section titled “1. Email Development Server”Add email development script to package.json
:
{ "scripts": { "email": "email dev", "email:build": "email export" }}
Run the email development server:
npm run email
pnpm run email
yarn email
bun run email
This opens a preview at http://localhost:3000
where you can see all your email templates.
2. Testing Emails
Section titled “2. Testing Emails”Test emails in development:
# Test welcome emailcurl -X POST http://localhost:3000/api/email/test \ -H "Content-Type: application/json" \
# Test magic link emailcurl -X POST http://localhost:3000/api/email/test \ -H "Content-Type: application/json" \
Advanced Features
Section titled “Advanced Features”1. Email Analytics with Webhooks
Section titled “1. Email Analytics with Webhooks”Set up webhooks to track email events:
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) { const body = await request.json();
// Verify webhook signature (recommended for production) const signature = request.headers.get('resend-signature');
switch (body.type) { case 'email.sent': console.log('Email sent:', body.data); break; case 'email.delivered': console.log('Email delivered:', body.data); break; case 'email.bounced': console.log('Email bounced:', body.data); break; case 'email.complained': console.log('Email complained:', body.data); break; }
return NextResponse.json({ received: true });}
2. Batch Email Sending
Section titled “2. Batch Email Sending”For sending multiple emails:
import { Resend } from 'resend';import config from '@/config';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendBatchEmails( emails: Array<{ to: string; subject: string; html: string; text: string; }>) { const promises = emails.map((email) => resend.emails.send({ from: config.resend.fromAdmin, ...email, }) );
return Promise.allSettled(promises);}
Production Best Practices
Section titled “Production Best Practices”1. Error Handling
Section titled “1. Error Handling”export async function sendEmailWithRetry(emailData: any, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await sendEmail(emailData); } catch (error) { console.error(`Email attempt ${attempt} failed:`, error);
if (attempt === maxRetries) { throw error; }
// Wait before retry (exponential backoff) await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt)) ); } }}
2. Rate Limiting
Section titled “2. Rate Limiting”const emailQueue: Array<() => Promise<any>> = [];let isProcessing = false;
export async function queueEmail(emailFn: () => Promise<any>) { emailQueue.push(emailFn);
if (!isProcessing) { processQueue(); }}
async function processQueue() { isProcessing = true;
while (emailQueue.length > 0) { const emailFn = emailQueue.shift(); if (emailFn) { try { await emailFn(); // Rate limit: wait 100ms between emails await new Promise((resolve) => setTimeout(resolve, 100)); } catch (error) { console.error('Queued email failed:', error); } } }
isProcessing = false;}
3. Environment-Specific Configuration
Section titled “3. Environment-Specific Configuration”export const getEmailConfig = () => { const isDev = process.env.NODE_ENV === 'development';
return { };};
Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”-
Emails going to spam
- Verify your domain with SPF/DKIM records
- Use a consistent “From” address
- Include plain text versions
- Avoid spam trigger words
-
API key errors
- Ensure
RESEND_API_KEY
is set correctly - Check API key permissions in Resend dashboard
- Ensure
-
Template rendering issues
- Ensure all React Email components are properly imported
- Check for TypeScript errors in templates
Testing Checklist
Section titled “Testing Checklist”- Domain verified in Resend
- DNS records configured
- Environment variables set
- Email templates render correctly
- API routes working
- Error handling implemented
- Rate limiting in place
LaunchKit’s email system provides a solid foundation for all your transactional email needs, from user onboarding to notifications and marketing campaigns!