Envello
Tutorial

Sending transactional email from Next.js with Envello

Envello Team·2026-07-29·7 min read

Next.js runs server-side code in API routes, Server Actions, and middleware. Sending transactional email from any of these is a single fetch() call, no external dependencies required. The trick is knowing where to put the call, how to handle the response, and what to watch out for in edge vs. Node.js runtimes.

Prerequisites

A Next.js 14+ project with App Router, an Envello account with a verified domain, and your API key in .env.local (which Next.js loads automatically and excludes from the client bundle when prefixed correctly).

  • ENVELLO_API_KEY=your_api_key_here (no NEXT_PUBLIC_ prefix; this must stay server-only)
  • ENVELLO_FROM=YourApp <[email protected]>

The basic send function

Create a server-only utility that wraps the API call. This function works in API routes, Server Actions, and any server-side context.

  • Create lib/email.ts (no 'use client' directive, this is server-only)
  • Export an async function sendEmail({ to, subject, html, text? })
  • Use fetch('https://api.envello.dev/emails', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ENVELLO_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ from: process.env.ENVELLO_FROM, to, subject, html, text }) })
  • Check response.ok; if false, throw with the status and response body for debugging
  • Return the parsed response (includes the message ID for tracking)

Sending from a Server Action

Server Actions are the most natural place to send transactional email in App Router applications. A form submission or button click triggers a server function that runs your email logic.

  • Create a Server Action with 'use server' at the top of the function or file
  • Validate the input (email address format, required fields) before calling sendEmail
  • Call sendEmail({ to, subject, html }) and handle the result
  • Return a success/error state that the client component can display
  • Server Actions run on the server only; process.env.ENVELLO_API_KEY is never exposed to the browser

Sending from an API route

For webhook handlers, cron jobs, or external integrations, use a Route Handler (app/api/.../route.ts) instead of a Server Action.

  • Create app/api/send-email/route.ts with a POST handler
  • Parse and validate the request body
  • Call your sendEmail utility
  • Return NextResponse.json({ success: true, messageId }) on success
  • Return NextResponse.json({ error }, { status: 400 }) on validation failure
  • Protect the endpoint with your own auth (API key, session check, or webhook signature verification)

React Email integration

If you're using React Email to design your transactional templates, the integration with Envello is straightforward: render the component to HTML, then pass it as the html field.

  • Install @react-email/components and @react-email/render
  • Create your email template as a React component (e.g., PasswordResetEmail.tsx)
  • Import render from @react-email/render
  • In your send function: const html = await render(<PasswordResetEmail resetUrl={url} />)
  • Pass the rendered html string to sendEmail({ to, subject, html })
  • No adapter or plugin needed; React Email renders to a string, and Envello accepts any HTML

Edge runtime considerations

Next.js API routes and middleware can run on the Edge Runtime (Vercel Edge Functions, Cloudflare Workers). The email send function works on the edge because it only uses fetch(), which is available in all edge runtimes.

Watch out for two things on the edge: environment variables may need explicit declaration in your deployment platform (Vercel requires them in the project settings, not just .env.local), and the edge runtime doesn't support Node.js-specific modules. As long as your email utility uses fetch and standard Web APIs, it runs on both Node.js and edge runtimes without changes.

Error handling patterns

Handle API errors at the utility layer so every call site gets consistent behavior.

  • 4xx errors (validation): don't retry. Log the response body (it tells you what's wrong: invalid address, unverified domain, suppressed recipient). Return a specific error to the caller.
  • 429 (rate limit): retry after the Retry-After delay. In a Server Action, this is unlikely unless you're sending bulk; in a high-traffic API route, add a short delay and retry once.
  • 5xx errors: retry once with a 1-second delay. If it fails again, log and return an error.
  • Network errors (fetch throws): retry once. If deployment is serverless, the function may have hit a cold start timeout; the retry usually succeeds.

Avoiding common mistakes

A few Next.js-specific pitfalls to watch for.

  • Don't prefix the API key with NEXT_PUBLIC_. That exposes it to the browser bundle. Server Actions and API routes access process.env directly; no prefix needed.
  • Don't import the email utility in a client component. Even if you don't call it from the client, the import pulls server-only code into the client bundle and may leak the API key or break the build.
  • Don't send email from middleware unless you have a specific reason. Middleware runs on every matching request and has tight execution time limits. Use a Server Action or API route instead.
  • Do use try/catch around every sendEmail call. A failed email send shouldn't crash the page or break the user's form submission flow.

Webhook handling

Create an API route to receive delivery event webhooks from Envello.

  • Create app/api/webhooks/envello/route.ts with a POST handler
  • Read the raw request body (await request.text()) and the X-Envello-Signature header
  • Verify the HMAC-SHA256 signature using the Web Crypto API (works in both Node.js and edge runtimes)
  • Parse the event and handle: delivered, bounced (suppress in your database), complained (suppress)
  • Return new Response('ok', { status: 200 }) immediately

Testing locally

Use Envello's test mode during development: sends are validated and logged but not delivered to real inboxes. Set a TEST_MODE flag in your .env.local and conditionally enable it in the API call.

For unit tests, mock the global fetch function to verify your utility sends the correct payload. Tools like msw (Mock Service Worker) work well for this in Next.js projects.

Free tool

Check your domain's SPF, DKIM, and DMARC records

Paste in a domain and see what's missing, plus the exact DNS records to fix it. Free, no account needed.

Check your domain →
Envello

EU-hosted transactional email, done right by default.