Menu

Email Notifications

NEXTY.DEV has a built-in email sending abstraction layer that supports both Resend and Cloudflare Email Sending, with pre-configured utility functions and email templates. You don't need to start from scratch—just follow this guide to:

  • Add your own email templates (e.g. order notifications, membership expiration reminders, etc.)
  • Send emails from server actions
  • Ensure sender information and unsubscribe links follow best practices

Prerequisites

Before using email notification features, complete the configuration for either email provider:

After configuration, switch the default provider via the DEFAULT_EMAIL_PROVIDER environment variable without changing any business code.

Step 1. Architecture Overview

In NEXTY.DEV, the core email-related files are:

  • Unified email entry: lib/mail/index.ts
  • Email provider implementations: lib/mail/providers/resend.ts, lib/mail/providers/cloudflare.ts
  • React email template rendering: lib/mail/render.tsx
  • Contact management: lib/mail/contacts.ts
  • Business scenario action examples: actions/newsletter/index.ts, actions/stripe/index.ts
  • React email templates: emails/*.tsx

We recommend routing all email sending calls through sendEmail from lib/mail rather than instantiating new Resend() or the Cloudflare SDK in your business logic. This approach allows you to:

  • Centrally manage API keys, sender email addresses, and unsubscribe logic
  • Automatically handle React template rendering and plain text fallback
  • Switch seamlessly between Resend and Cloudflare
  • Automatically add recipients to the contact list (Resend syncs to Contacts, Cloudflare records in Redis)

Step 2. Environment Variables & Basic Configuration

Before writing code, ensure the following environment variables are configured:

Environment VariableDescription
DEFAULT_EMAIL_PROVIDERDefault email provider: resend or cloudflare, defaults to resend
DEFAULT_ADMIN_NAMESender name (e.g. NEXTY.DEV Team)
DEFAULT_ADMIN_EMAILSender email address (e.g. [email protected])
NEXT_PUBLIC_SITE_URLFull site domain for generating absolute links in emails

If you use Resend, you also need:

  • RESEND_API_KEY: Your Resend API Key

If you use Cloudflare Email Sending, you also need:

  • CLOUDFLARE_EMAIL_API_TOKEN: Cloudflare API Token
  • CLOUDFLARE_ACCOUNT_ID: Cloudflare account ID

In your local development environment, configure these in .env.local (Cloudflare example):

DEFAULT_EMAIL_PROVIDER=cloudflare
DEFAULT_ADMIN_NAME="NEXTY.DEV Team"
DEFAULT_ADMIN_EMAIL="[email protected]"
NEXT_PUBLIC_SITE_URL=https://yourdomain.com
 
CLOUDFLARE_EMAIL_API_TOKEN=your_cf_email_api_token
CLOUDFLARE_ACCOUNT_ID=your_cf_account_id

Step 3. Core Sending Utility: sendEmail

lib/mail provides the unified sendEmail method. Its parameter definition is as follows (simplified):

interface SendEmailOptions {
  to: string | string[];                         // Recipient email address(es)
  from: string | { email: string; name?: string }; // Sender
  subject: string;                               // Email subject
  html?: string;                                 // HTML content (choose one with react)
  text?: string;                                 // Plain text content (optional)
  replyTo?: string | { email: string; name?: string }; // Reply-to address
  cc?: string | string[];                        // CC
  bcc?: string | string[];                       // BCC
  headers?: Record<string, string>;              // Custom email headers
  attachments?: EmailAttachment[];               // Attachments
  react?: React.ComponentType<any> | React.ReactElement; // React email template
  reactProps?: Record<string, any>;              // Props passed to the template
  addToContacts?: boolean;                       // Whether to add the recipient to the contact list after sending
}

Internally, it handles the following:

  • Returns an error if to is empty
  • Returns an error if email environment variables are not configured
  • If a React template is used, automatically renders it to HTML and generates a plain text fallback
  • Composes the sender using DEFAULT_ADMIN_NAME + DEFAULT_ADMIN_EMAIL (you can also override this at call time)
  • Routes to the corresponding provider based on DEFAULT_EMAIL_PROVIDER
  • Retries once automatically based on the HTTP status code when sending fails

Explicitly Specify a Provider

The second parameter of sendEmail can be used to explicitly specify a provider, commonly used for A/B testing or scenarios requiring different channels:

import { sendEmail } from "@/lib/mail";
 
// Use the default provider
await sendEmail({ ...options });
 
// Explicitly use Resend
await sendEmail({ ...options }, "resend");
 
// Explicitly use Cloudflare
await sendEmail({ ...options }, "cloudflare");

Step 4. Built-in Example: Newsletter Welcome Email

actions/newsletter/index.ts demonstrates a complete example with the following flow:

  • Normalizes and validates user-inputted email addresses
  • Implements rate limiting (prevents malicious API abuse)
  • Prepares email subject and unsubscribe link
  • Calls sendEmail to send the NewsletterWelcomeEmail template

Logic snippet:

await sendEmail({
  to: normalizedEmail,
  from: {
    email: process.env.DEFAULT_ADMIN_EMAIL!,
    name: process.env.DEFAULT_ADMIN_NAME || siteConfig.name,
  },
  subject: `Welcome to ${siteConfig.name} Newsletter!`,
  react: NewsletterWelcomeEmail,
  reactProps: {
    email: normalizedEmail,
    unsubscribeLink,
  },
  headers: {
    "List-Unsubscribe": `<${unsubscribeLink}>`,
    "List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
  },
  addToContacts: true,
});

You can fully replicate this pattern in your own business logic by simply replacing:

  • Template component (react field)
  • Template props (reactProps field)
  • Subject line copy

Step 5. Contact Management

lib/mail/contacts.ts provides a unified contact management interface:

  • addContact(email, provider): Called after a successful send. Resend syncs to Resend Contacts, Cloudflare records in a Redis set.
  • removeContact(email): Called when unsubscribing. Cleans up both Resend Contacts and Redis records so unsubscribe remains effective after switching providers.
  • isContact(email): Checks whether the email is in the Redis contact set.

Business code generally does not need to call these methods directly. sendEmail with addToContacts: true automatically handles adding contacts.

Step 6. Notes

  • Maintain consistent styling across different email templates to benefit your brand image.
  • Email templates use siteConfig.name and siteConfig.url for brand information, so ensure your configuration in config/site.ts is correct.
  • Email templates cannot use lucide-react. Use SVG icons or absolute image links instead.
  • Email templates cannot use Tailwind CSS. Use inline styles instead.
  • Email templates cannot use relative links. Always use absolute links.
  • Resend's free plan allows you to store 1,000 contacts and send 3,000 emails per month. If this doesn't meet your needs, you'll need to upgrade your plan.
  • Cloudflare Email Sending requires the sender domain to have passed DNS verification. The current main branch sends via REST API, suitable for servers or Vercel deployments. For Cloudflare Workers deployments, use the cf-pg branch.

When creating new email templates, simply have AI reference existing email templates to generate new ones, no need for manual review of every detail.