Codapult uses Resend for transactional email delivery and React Email for composable, type-safe email templates. Emails are sent through the background job system for reliability and non-blocking performance.
Setup
Set these environment variables in .env.local:
| Variable | Description |
| ---------------- | --------------------------------------------------------------------------- |
| RESEND_API_KEY | Your Resend API key from resend.com/api-keys |
| EMAIL_FROM | Default sender address (e.g. Your App <[email protected]>) |
Sending Email
Use the enqueueEmail helper from @/lib/jobs to send emails via the background job system:
import { render } from '@react-email/render';
import { enqueueEmail } from '@/lib/jobs';
import { WelcomeEmail } from '@/lib/email/templates/WelcomeEmail';
const html = await render(
<WelcomeEmail userName="Jane" dashboardUrl="https://app.example.com/dashboard" />,
);
await enqueueEmail('[email protected]', 'Welcome to the team!', html);
The enqueueEmail function accepts three arguments — to, subject, and html — and dispatches a send-email background job that delivers via Resend.
Templates
Email templates live in src/lib/email/templates/ and are standard React components built with @react-email/components:
| Template | Description |
| ------------------------ | -------------------------------------------------- |
| BaseLayout.tsx | Shared layout wrapper (header, footer, brand name) |
| WelcomeEmail.tsx | New user welcome email |
| BillingEmail.tsx | Billing and subscription notifications |
| ResetPasswordEmail.tsx | Password reset link |
| InvitationEmail.tsx | Team invitation email |
Template Pattern
Every template is a React component that wraps content in the shared BaseLayout:
import { Button, Heading, Section, Text } from '@react-email/components';
import { BaseLayout } from './BaseLayout';
interface WelcomeEmailProps {
userName: string;
dashboardUrl: string;
}
export function WelcomeEmail({ userName, dashboardUrl }: WelcomeEmailProps) {
return (
<BaseLayout preview={`Welcome to the team, ${userName}!`}>
<Heading style={{ fontSize: '24px', fontWeight: '700', color: '#111' }}>
Welcome, {userName}!
</Heading>
<Text style={{ fontSize: '16px', color: '#555', lineHeight: '1.6' }}>
Your account is ready. Click the button below to get started.
</Text>
<Section style={{ textAlign: 'center', margin: '24px 0' }}>
<Button
style={{
backgroundColor: '#111',
borderRadius: '8px',
color: '#fff',
fontSize: '16px',
fontWeight: '600',
padding: '12px 24px',
}}
href={dashboardUrl}>
Go to Dashboard
</Button>
</Section>
</BaseLayout>
);
}
Creating a New Template
- Create a new file in
src/lib/email/templates/(e.g.NotificationEmail.tsx) - Define a props interface for the template data
- Wrap content in
<BaseLayout preview="...">for consistent branding - Use components from
@react-email/components:Html,Head,Body,Container,Section,Text,Button,Heading,Hr,Preview,Link,Img - Style with inline
React.CSSPropertiesobjects (email clients don't support CSS classes)
Base Layout
The BaseLayout component provides consistent structure across all emails:
- App name in the header (reads from
NEXT_PUBLIC_APP_NAME) - Centered container with max width of 600px
- Footer with copyright notice
- Preview text for email client snippets
Custom Sending Domains
Codapult integrates with the Resend Domains API to let you verify and manage custom sending domains directly from the admin panel.
Admin Panel
Navigate to Admin → Email Domains to:
- Add a new sending domain
- View DNS records required for verification (SPF, DKIM, DMARC)
- Check verification status
- Remove a domain
How It Works
- Add your domain in the admin panel
- Resend returns the required DNS records
- Add the DNS records to your domain provider
- Resend verifies the records automatically
- Start sending from
@yourdomain.comaddresses
Removing the Module
Email is a removable module. Use the setup wizard (npx @codapult/cli setup) to strip it, or see docs/MODULES.md for manual removal steps. If removed, any code that calls enqueueEmail must be updated or removed as well.