DO NOT upgrade any Resend dependencies unless you’re following their official migration guide and understand the breaking changes. Upgrading without proper migration can break your email sending functionality and webhook handling. The current Resend version in the boilerplate is tested and stable.
This guide covers working with email sending using Resend in Kaizen.
Kaizen includes a complete email infrastructure that allows you to send transactional and marketing emails to your users. The system uses Resend, a modern email service that provides reliable delivery, webhook events, and seamless integration with Convex through the @convex-dev/resend
component.
Prerequisites
Enable Email Feature
First, make sure email functionality is enabled in your config.ts
:
features: {
email: true, // Enable email functionality
}
services: {
resend: { enabled: true },
}
Setup Resend Account
- Create an account at Resend
- Generate an API key from the API Keys section
- For production, verify your domain in the Resend dashboard
Environment Variables Setup
Important: All email environment variables must be set in your Convex dashboard, not in your local .env.local
file.
- Go to your Convex project dashboard
- Navigate to Settings > Environment Variables
- Add the following variables:
# Required for email functionality
RESEND_API_KEY=re_your_api_key_here
RESEND_WEBHOOK_SECRET=whsec_your_webhook_secret_here
# Email configuration
SENDER_EMAIL=onboarding@resend.dev # For testing
COMPANY_NAME=Your Company Name # Used in email templates
For testing, you can use onboarding@resend.dev
as the sender email. For production, verify your custom domain and use noreply@yourdomain.com
.
Testing Emails Locally
Kaizen includes a built-in test email form in the dashboard:
-
Start your development server:
-
Sign in to your application
-
Navigate to the dashboard - you should see a “Test Email” form
-
Fill in the form with:
- To Email: Your email address
- Subject: Custom subject (optional)
- Message: Custom message (optional)
-
Click “Send Test Email”
Using Convex Functions Directly
You can also test emails using Convex functions:
// In your Convex dashboard functions tab, you can run:
import { api } from "./convex/_generated/api";
// Send a test email
await sendTestEmailToAddress({
toEmail: "your-email@example.com",
subject: "Test Email",
message: "<h1>Hello!</h1><p>This is a test email.</p>"
});
Email Types and Templates
Welcome Email
Kaizen automatically sends welcome emails when new users sign up:
// Automatically triggered on user registration
export const sendWelcomeEmail = internalMutation({
args: { email: v.string(), name: v.string() },
handler: async (ctx, { email, name }) => {
const fromEmail = process.env.SENDER_EMAIL || "welcome@resend.dev";
const companyName = process.env.COMPANY_NAME || "Kaizen";
await resend.sendEmail(
ctx,
`${companyName} <${fromEmail}>`,
email,
`Welcome to ${companyName}, ${name}!`,
`<h1>Welcome aboard, ${name}!</h1><p>We're excited to have you with us at ${companyName}.</p>`
);
},
});
Custom Email Templates
To add your own email templates, create new functions in convex/sendEmails.ts
:
export const sendPasswordResetEmail = internalMutation({
args: { email: v.string(), resetLink: v.string() },
handler: async (ctx, { email, resetLink }) => {
const fromEmail = process.env.SENDER_EMAIL || "noreply@resend.dev";
const companyName = process.env.COMPANY_NAME || "Kaizen";
await resend.sendEmail(
ctx,
`${companyName} <${fromEmail}>`,
email,
"Password Reset Request",
`
<h1>Password Reset</h1>
<p>Click the link below to reset your password:</p>
<a href="${resetLink}">Reset Password</a>
<p>If you didn't request this, please ignore this email.</p>
`
);
},
});
Webhook Handling
Kaizen automatically handles Resend webhooks for email events:
Setup Webhook in Resend Dashboard
- In your Resend dashboard, go to Webhooks
- Add a new webhook endpoint:
<your-convex-http-url>/resend-webhook
- Select the events you want to track
- Copy the webhook secret and add it to your Convex environment variables
Email Event Processing
Email events are automatically processed:
export const handleEmailEvent = internalMutation({
args: {
id: vEmailId,
event: vEmailEvent,
},
handler: async (ctx, args) => {
console.log("Email event received:", args.id, args.event);
// Handle different email events
switch (args.event.type) {
case "email.delivered":
// Handle successful delivery
break;
case "email.bounced":
// Handle bounced emails
break;
case "email.complained":
// Handle spam complaints
break;
}
},
});
Production Setup
Domain Verification
For production use, you’ll need to verify your domain:
- In Resend dashboard, go to Domains
- Add your domain (e.g.,
yourdomain.com
)
- Add the required DNS records to your domain provider
- Wait for verification (usually a few minutes)
- Update your
SENDER_EMAIL
to use your domain: noreply@yourdomain.com
Production Environment Variables
Update your production Convex environment with:
SENDER_EMAIL=noreply@yourdomain.com
COMPANY_NAME=Your Company Name
RESEND_API_KEY=re_your_production_api_key
RESEND_WEBHOOK_SECRET=whsec_your_production_webhook_secret
Common Issues & Troubleshooting
Emails not being sent
- Check Convex environment variables: Ensure
RESEND_API_KEY
is set in Convex dashboard, not local .env.local
- Verify email feature is enabled: Check
config.ts
has email: true
and resend: { enabled: true }
- Check Convex logs: Look for error messages in your Convex function logs
- Authentication required: Ensure user is authenticated when calling
sendTestEmailToAddress
Domain verification issues
- DNS propagation: Wait up to 24 hours for DNS changes to propagate
- Correct DNS records: Double-check you’ve added all required TXT, CNAME, and MX records
- Use verified domain: Only send from verified domains in production
Emails going to spam
- Domain authentication: Ensure SPF, DKIM, and DMARC records are properly configured
- Content quality: Avoid spam triggers (excessive exclamation marks, all caps, spam-like phrases)
- Sending reputation: Start with low volume and gradually increase
- List hygiene: Remove bounced emails and unsubscribed users
Webhook issues
- Correct endpoint: Ensure webhook URL is
<your-convex-http-url>/resend-webhook
- Webhook secret: Verify
RESEND_WEBHOOK_SECRET
matches your Resend dashboard
- Event selection: Choose the email events you want to track
Common Error Messages
“Must be authenticated to send test emails”
- Solution: Ensure user is signed in before calling test email functions
“Domain not verified”
- Solution: Verify your domain in Resend dashboard before sending from custom domain
“Invalid API key”
- Solution: Check your
RESEND_API_KEY
is correct and active
For transactional emails, always provide a clear way for users to manage their email preferences or unsubscribe. Consider implementing an email preferences page in your dashboard.
Email Best Practices
Template Design
- Use responsive HTML templates
- Include both HTML and plain text versions
- Keep subject lines under 50 characters
- Use clear call-to-action buttons
Deliverability
- Implement double opt-in for marketing emails
- Monitor bounce rates and remove invalid emails
- Include proper unsubscribe links
- Use consistent sender names and email addresses
- Send emails asynchronously using Convex schedulers
- Batch process large email lists
- Implement retry logic for failed sends
- Monitor email metrics and adjust strategies accordingly
Responses are generated using AI and may contain mistakes.