Skip to content

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.

LaunchKit comes pre-configured with Resend. You just need to:

  1. Get your Resend API key
  2. Configure your domain
  3. Set up environment variables
  4. Start sending emails
  1. Sign up at resend.com (free tier includes 3,000 emails/month)
  2. Verify your domain or use Resend’s testing domain ([email protected])
  3. Generate an API key from the API Keys section

Add your Resend API key to your environment variables:

.env.local
RESEND_API_KEY=re_your_api_key_here

For production, verify your domain in Resend dashboard:

  1. Add your domain in Resend dashboard
  2. Add DNS records (SPF, DKIM, DMARC)
  3. Wait for verification (usually takes a few minutes)

Required DNS Records:

# SPF Record
TXT @ "v=spf1 include:_spf.resend.com ~all"
# DKIM Record
TXT resend._domainkey "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC..."
# DMARC Record
TXT _dmarc "v=DMARC1; p=quarantine; rua=mailto:[email protected]"

LaunchKit uses a centralized config for email settings. Update your config.ts:

config.ts
const config = {
// ... other config
resend: {
// Email 'From' field for system emails (magic links, password resets)
fromNoReply: `${appName} <[email protected]>`,
// Email 'From' field for personal emails (updates, announcements)
fromAdmin: `Your Name at ${appName} <[email protected]>`,
// Support email for customer inquiries
supportEmail: '[email protected]',
},
};
export default config;

LaunchKit includes a pre-built email service at libs/resend.ts:

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;
};

Install React Email for beautiful, component-based templates:

Terminal window
npm install @react-email/components @react-email/render
npm install react-email -D

Create your first email template:

emails/WelcomeEmail.tsx
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>
);
}
// Styles
const 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',
};
emails/MagicLinkEmail.tsx
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,
};

Create a comprehensive email service:

libs/email-service.ts
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 email
export 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 email
export 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 email
export 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 email
export 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,
});
}

Update your lead API route to send emails:

app/api/lead/route.ts
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 });
}
}

Create a testing route for development:

app/api/email/test/route.ts
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 }
);
}
}

Add email development script to package.json:

{
"scripts": {
"email": "email dev",
"email:build": "email export"
}
}

Run the email development server:

Terminal window
npm run email

This opens a preview at http://localhost:3000 where you can see all your email templates.

Test emails in development:

Terminal window
# Test welcome email
curl -X POST http://localhost:3000/api/email/test \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","type":"welcome","name":"John Doe"}'
# Test magic link email
curl -X POST http://localhost:3000/api/email/test \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","type":"magic-link"}'

Set up webhooks to track email events:

app/api/webhooks/resend/route.ts
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 });
}

For sending multiple emails:

libs/batch-email.ts
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);
}
libs/email-with-retry.ts
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))
);
}
}
}
libs/email-rate-limiter.ts
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;
}
libs/email-config.ts
export const getEmailConfig = () => {
const isDev = process.env.NODE_ENV === 'development';
return {
from: isDev ? 'Test <[email protected]>' : config.resend.fromAdmin,
replyTo: isDev ? '[email protected]' : config.resend.supportEmail,
};
};
  1. Emails going to spam

    • Verify your domain with SPF/DKIM records
    • Use a consistent “From” address
    • Include plain text versions
    • Avoid spam trigger words
  2. API key errors

    • Ensure RESEND_API_KEY is set correctly
    • Check API key permissions in Resend dashboard
  3. Template rendering issues

    • Ensure all React Email components are properly imported
    • Check for TypeScript errors in templates
  • 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!