Skip to content
Back to Vibe coding

AI app builder

Updated

Add email to your v0 app with Lumail

v0 generates Next.js apps, which already have a server: route handlers and server actions. The prompt only has to keep the send there and the key out of anything prefixed `NEXT_PUBLIC_`.

TL;DR

Paste the prompt below into v0. It installs the lumail package, creates a server-only client in lib/lumail.ts that reads LUMAIL_API_KEY, and sends from a server action with an idempotency key. Add the key to the project's environment variables in v0 and to the Vercel project when you deploy.

1. Prompt v0

Paste this into the v0 chat. It names the Next.js primitives explicitly, so v0 does not reach for a client-side fetch.

Prompt for v0
Add transactional email to this app with Lumail (https://lumail.io). Stack: Next.js App Router generated by v0. Create lib/lumail.ts with import "server-only" and send from a server action ("use server") or a route handler, never from a client component. Rules: - Send email only from server-side code. Never call Lumail from the browser and never expose the API key to client code. - Read the API key from the LUMAIL_API_KEY environment variable. It is a project environment variable; never prefix it with NEXT_PUBLIC_. Throw a clear error if it is missing. - In TypeScript/JavaScript use the official `lumail` npm package: `new Lumail({ apiKey })`, then `lumail.emails.send({ from, to, subject, html })`. - Elsewhere call the REST API: POST https://lumail.io/api/v2/emails with `Authorization: Bearer <LUMAIL_API_KEY>` and a JSON body. - Pass exactly one body format: `html`, `markdown` or `tiptap`. Add `text` as a plain-text fallback when sending html. - `to` is a single recipient. Loop or use `lumail.emails.batch` (max 100) for several people. - The SDK returns `{ data, error }` and never throws on HTTP errors. Check `error` and log `error.name` and `error.message`. - Pass an `idempotencyKey` (header `Idempotency-Key` over REST) built from the event that triggered the email, such as `welcome:<userId>`. - `from` must use a domain verified in my Lumail organization. Put it in a LUMAIL_FROM environment variable, for example `Acme <[email protected]>`. First task: send a welcome email when a user signs up. Show me which files you changed and how to test it.

2. Put the API key in the right place

Create a lum_ token in Lumail under API tokens. Add LUMAIL_API_KEY and LUMAIL_FROM to the project's environment variables in v0 so the preview can send, and to the Vercel project's environment variables when you deploy.

Anything prefixed NEXT_PUBLIC_ is inlined into the browser bundle. The Lumail key must never use that prefix; the server-only import makes the build fail if a client component imports the client by mistake.

.env
# Server-side only. Never prefix with VITE_ or NEXT_PUBLIC_. LUMAIL_API_KEY=lum_your_api_token LUMAIL_FROM="Acme <[email protected]>"

3. The code v0 should generate

Two files: the client module and a server action. A form or button in a client component can call the action directly.

lib/lumail.ts
import "server-only"; import { Lumail } from "lumail"; const apiKey = process.env.LUMAIL_API_KEY; if (!apiKey) { throw new Error("LUMAIL_API_KEY is not set"); } export const lumail = new Lumail({ apiKey });
app/actions/send-welcome.ts
"use server"; import { lumail } from "@/lib/lumail"; export async function sendWelcome(user: { id: string; email: string }) { const { data, error } = await lumail.emails.send( { from: process.env.LUMAIL_FROM ?? "Acme <[email protected]>", to: user.email, subject: "Welcome to Acme", html: "<p>Thanks for signing up.</p>", text: "Thanks for signing up.", }, { idempotencyKey: `welcome:${user.id}` }, ); if (error) { console.error("Lumail send failed", error.name, error.message); return { ok: false }; } return { ok: true, id: data.id }; }

4. Lumail MCP server and agent plugins

v0 builds and hosts your app in the cloud, so your app talks to Lumail through the API with a token, not through MCP. The Lumail MCP server is for agents that run where you work: Claude Code, Codex, ChatGPT, Cursor or the Lumail CLI.

That split is useful once the app is live. Ask Claude Code or ChatGPT to check your domain's DNS status, look up a subscriber who did not get their email, or draft the next newsletter, while the app keeps sending through the API.

5. Verify your sending domain

Lumail only sends from a domain you have verified. Add the domain in your organization's Domains settings, then publish the SPF, DKIM and DMARC records it shows at your DNS provider. Until the domain verifies, every send fails with an error saying the domain is not authorized or verified.

Use a subdomain such as mail.yourdomain.com if your root domain already sends from another provider. Start DMARC at p=none, then tighten it once reports look clean.

Common pitfalls

  • `NEXT_PUBLIC_LUMAIL_API_KEY`. If the generated code reads the key in a client component, it will suggest this prefix. Refuse it, rotate the key if it was ever deployed, and move the call to a server action.
  • Server actions are public endpoints. Anyone can call an exported action with any arguments. Derive the recipient from the session, not from a value the browser passes in.
  • Client-side sends. If the AI imports lumail in a React component or uses fetch to the Lumail API from the browser, the key ships to every visitor. Move it to a server route or function and rotate the token.
  • Unverified `from` domain. Sends from a domain that is not verified in the same organization are rejected with a 400 that names the domain. Verify it first, or use the exact address Lumail shows you.
  • Treating `{ error }` as an exception. The SDK never throws on HTTP errors. Code that only wraps the call in try/catch silently drops failures.
  • Duplicate emails on retry. Without an idempotency key, a retried request or double-clicked button can send twice.

Frequently asked questions

Does a v0 app need a separate backend to send email?

No. v0 generates Next.js, and server actions and route handlers run on the server. Send from there with the lumail package.

Where do I add the Lumail API key for a v0 project?

In the project's environment variables in v0 for previews, and in the Vercel project's environment variables for deployments. Never with a NEXT_PUBLIC_ prefix.

Why import server-only in lib/lumail.ts?

It makes the build fail if a client component imports the module, so the API key can never be bundled into browser code by accident.

Can v0 use the Lumail MCP server?

v0 runs in the cloud, so connect the MCP server to a local agent such as Claude Code, Codex, ChatGPT or Cursor. Once you pull the v0 project into your editor, the Cursor guide applies.

How do I preview the email without sending it?

Send to [email protected] or any .test domain. Lumail validates and queues the email and returns an id, but never delivers it.

Keep building

Ship email from your v0 app today.

3,000 emails a month free. Transactional and marketing email on one verified domain, with unlimited subscribers on every plan.