Skip to content
Back to Vibe coding

AI code editor

Updated

Add email to your Windsurf app with Lumail

Windsurf's Cascade agent can wire Lumail into any codebase in one pass if the prompt spells out the rules: server-side only, one body format, check the error, and an idempotency key on every send.

TL;DR

Paste the prompt below into Cascade. It reads lumail.io/integration/install, installs the lumail package, reads LUMAIL_API_KEY from .env in server code, and sends with lumail.emails.send. Run npx lumail setup or add https://lumail.io/mcp as serverUrl in ~/.codeium/windsurf/mcp_config.json to give Cascade the Lumail MCP tools.

1. Prompt Windsurf

Open Cascade and paste this. The install guide it references detects your framework, so the same prompt works for Next.js, Express, SvelteKit or anything else in the repo.

Prompt for Windsurf
Add transactional email to this app with Lumail (https://lumail.io). Stack: the framework already used in this repository. Detect it before writing code. 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. Add it to .env (and .env.example with a placeholder) and make sure .env is git-ignored. 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]>`. - Before writing code, read lumail.io/integration/install and follow it. 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. Put it in .env, keep .env git-ignored, and commit a placeholder in .env.example. Set the same variable on your hosting provider before you deploy.

.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 Windsurf should generate

Expect one server-only module that creates the client and a function that sends with an idempotency key and checks error. Compare with the framework guides if Cascade invents its own wrapper.

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

4. Lumail MCP server and agent plugins

With the Lumail MCP server connected, Cascade can read your organization: domains and DNS status, subscribers, campaigns and drafts. The OAuth endpoint https://lumail.io/mcp has no send or delete tools.

npx lumail setup writes the entry below for you and installs the Lumail skill. Windsurf asks you to sign in with OAuth the first time Cascade uses the server. To connect it by hand, add the entry to ~/.codeium/windsurf/mcp_config.json.

If your Windsurf version only accepts command-based servers, use the bridge instead: npx -y mcp-remote https://lumail.io/mcp.

Terminal
npx lumail setup
~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "lumail": { "serverUrl": "https://lumail.io/mcp" } } }

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

  • Stale MCP config. After editing mcp_config.json, refresh the MCP servers in Windsurf so Cascade picks up the change.
  • 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 Windsurf need the MCP server for my app to send email?

No. The app sends through the lumail SDK or REST API with an API token. The MCP server only gives Cascade access to your Lumail organization while you build.

How do I add the Lumail MCP server to Windsurf?

Run npx lumail setup, or add an entry named lumail with serverUrl https://lumail.io/mcp under mcpServers in ~/.codeium/windsurf/mcp_config.json. Windsurf asks you to sign in with OAuth the first time the server is used.

Can Cascade send emails through the MCP server?

Not through https://lumail.io/mcp, which is limited to reading and drafting. The token endpoint at https://lumail.io/api/mcp/sse has the full tool set, including sends, so connect it only if you want that.

Where does the API key go?

In .env for local development and in your host's environment variables in production. Only server code reads it; never prefix it with VITE_ or NEXT_PUBLIC_.

How do I test the generated code safely?

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 Windsurf app today.

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