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

  1. Create an account at Resend
  2. Generate an API key from the API Keys section
  3. 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.

  1. Go to your Convex project dashboard
  2. Navigate to Settings > Environment Variables
  3. 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

Using the Test Email Form

Kaizen includes a built-in test email form in the dashboard:

  1. Start your development server:

    npm run dev
    
  2. Sign in to your application

  3. Navigate to the dashboard - you should see a “Test Email” form

  4. Fill in the form with:

    • To Email: Your email address
    • Subject: Custom subject (optional)
    • Message: Custom message (optional)
  5. 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

  1. In your Resend dashboard, go to Webhooks
  2. Add a new webhook endpoint: <your-convex-http-url>/resend-webhook
  3. Select the events you want to track
  4. 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:

  1. In Resend dashboard, go to Domains
  2. Add your domain (e.g., yourdomain.com)
  3. Add the required DNS records to your domain provider
  4. Wait for verification (usually a few minutes)
  5. 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

Performance

  • Send emails asynchronously using Convex schedulers
  • Batch process large email lists
  • Implement retry logic for failed sends
  • Monitor email metrics and adjust strategies accordingly