Sending Emails in the Next.js Supabase SaaS Starter Kit
Send transactional emails from your Next.js Supabase application using the MakerKit mailer API. Learn the email schema, error handling, and best practices.
The @kit/mailers package provides a simple, provider-agnostic API for sending emails. Use it in Server Actions, API routes, or any server-side code to send transactional emails.
Basic Usage
Import getMailer and call sendEmail with your email data:
import { getMailer } from '@kit/mailers';async function sendWelcomeEmail(userEmail: string) { const mailer = await getMailer(); await mailer.sendEmail({ to: userEmail, from: process.env.EMAIL_SENDER!, subject: 'Welcome to our platform', text: 'Thanks for signing up! We are excited to have you.', });}The getMailer function returns the configured mailer instance (Nodemailer or Resend) based on your MAILER_PROVIDER environment variable.
Email Schema
The sendEmail method accepts an object validated by this Zod schema:
// Simplified representation of the schematype EmailData = { to: string; // Recipient email (must be valid email format) from: string; // Sender (e.g., "App Name <noreply@app.com>") subject: string; // Email subject line} & ( | { text: string } // Plain text body | { html: string } // HTML body);You must provide exactly one of text or html. This is a discriminated union, not optional fields. Providing both properties or neither will cause a validation error at runtime.
Sending HTML Emails
For rich email content, use the html property:
import { getMailer } from '@kit/mailers';async function sendHtmlEmail(to: string) { const mailer = await getMailer(); await mailer.sendEmail({ to, from: process.env.EMAIL_SENDER!, subject: 'Your weekly summary', html: ` <h1>Weekly Summary</h1> <p>Here's what happened this week:</p> <ul> <li>5 new team members joined</li> <li>12 tasks completed</li> </ul> `, });}For complex HTML emails, use React Email templates instead of inline HTML strings.
Using Email Templates
MakerKit includes pre-built email templates in the @kit/email-templates package. These templates use React Email and support internationalization:
import { getMailer } from '@kit/mailers';import { renderInviteEmail } from '@kit/email-templates';async function sendTeamInvitation(params: { invitedEmail: string; teamName: string; inviterName: string; inviteLink: string;}) { const mailer = await getMailer(); // Render the React Email template to HTML const { html, subject } = await renderInviteEmail({ teamName: params.teamName, inviter: params.inviterName, invitedUserEmail: params.invitedEmail, link: params.inviteLink, productName: 'Your App Name', }); await mailer.sendEmail({ to: params.invitedEmail, from: process.env.EMAIL_SENDER!, subject, html, });}See the Email Templates guide for creating custom templates.
Error Handling
The sendEmail method returns a Promise that rejects on failure. Always wrap email sending in try-catch:
import { getMailer } from '@kit/mailers';async function sendEmailSafely(to: string, subject: string, text: string) { try { const mailer = await getMailer(); await mailer.sendEmail({ to, from: process.env.EMAIL_SENDER!, subject, text, }); return { success: true }; } catch (error) { console.error('Failed to send email:', error); // Log to your error tracking service // Sentry.captureException(error); return { success: false, error: 'Failed to send email' }; }}Common Error Causes
| Error | Cause | Solution |
|---|---|---|
| Validation error | Invalid email format or missing fields | Check to is a valid email, ensure text or html is provided |
| Authentication failed | Wrong SMTP credentials | Verify EMAIL_USER and EMAIL_PASSWORD |
| Connection refused | SMTP server unreachable | Check EMAIL_HOST and EMAIL_PORT, verify network access |
| Rate limited | Too many emails sent | Implement rate limiting, use a queue for bulk sends |
Using in Server Actions
Email sending works in Next.js Server Actions:
app/actions/send-notification.ts
'use server';import { getMailer } from '@kit/mailers';export async function sendNotificationAction(formData: FormData) { const email = formData.get('email') as string; const message = formData.get('message') as string; const mailer = await getMailer(); await mailer.sendEmail({ to: email, from: process.env.EMAIL_SENDER!, subject: 'New notification', text: message, }); return { success: true };}Using in API Routes
For webhook handlers or external integrations:
app/api/webhooks/send-email/route.ts
import { NextResponse } from 'next/server';import { getMailer } from '@kit/mailers';export async function POST(request: Request) { const { to, subject, message } = await request.json(); try { const mailer = await getMailer(); await mailer.sendEmail({ to, from: process.env.EMAIL_SENDER!, subject, text: message, }); return NextResponse.json({ success: true }); } catch (error) { return NextResponse.json( { error: 'Failed to send email' }, { status: 500 } ); }}Best Practices
Use Environment Variables for Sender
Never hardcode the sender email:
// Goodfrom: process.env.EMAIL_SENDER!// Badfrom: 'noreply@example.com'Validate Recipient Emails
Before sending, validate that the recipient email exists in your system:
import { getMailer } from '@kit/mailers';async function sendToUser(userId: string, subject: string, text: string) { // Fetch user from database first const user = await getUserById(userId); if (!user?.email) { throw new Error('User has no email address'); } const mailer = await getMailer(); await mailer.sendEmail({ to: user.email, from: process.env.EMAIL_SENDER!, subject, text, });}Queue Bulk Emails
For sending many emails, use a background job queue to avoid timeouts and handle retries:
// Instead of this:for (const user of users) { await sendEmail(user.email); // Slow, no retry handling}// Use a queue like Trigger.dev, Inngest, or BullMQawait emailQueue.addBulk( users.map(user => ({ name: 'send-email', data: { email: user.email, template: 'weekly-digest' }, })));Next Steps
- Create custom email templates with React Email
- Build a custom mailer for other providers
- Test emails locally with Mailpit