Skip to content
Back to Vibe coding

AI app builder

Updated

Add email to your Replit app with Lumail

Replit apps usually have a real server, in Node or Python, so the send is straightforward. What goes wrong is the secret: it has to live in Replit Secrets, and the deployed app has to see it too.

TL;DR

Add LUMAIL_API_KEY in Replit's Secrets, then paste the prompt below into Replit Agent. In Node it uses the lumail package; in Python it POSTs to https://lumail.io/api/v2/emails with requests. Before publishing, check that the deployment has the same secrets.

1. Prompt Replit

Add the secret first so the Agent can run and test the code right away, then paste this.

Prompt for Replit
Add transactional email to this app with Lumail (https://lumail.io). Stack: the language this Repl already uses. In Node.js use the lumail package; in Python use requests against the REST API. 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 already stored in Replit Secrets; read it from the environment and never print it. 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. Open the Secrets tool in your Repl and add LUMAIL_API_KEY and LUMAIL_FROM. Secrets are exposed to your code as environment variables and are not copied when someone forks a public Repl.

Deployments run separately from the workspace. Before you publish, open the deployment settings and confirm the Lumail secrets are listed there.

3. The code Replit should generate

For a Node.js Repl, the module below is what the Agent should produce. For Python there is no Lumail SDK; a small function around requests does the same job.

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 }; }
send_welcome.py
import os import requests API_KEY = os.environ["LUMAIL_API_KEY"] FROM = os.environ.get("LUMAIL_FROM", "Acme <[email protected]>") def send_welcome(user_id: str, email: str) -> str | None: response = requests.post( "https://lumail.io/api/v2/emails", headers={ "Authorization": f"Bearer {API_KEY}", "Idempotency-Key": f"welcome:{user_id}", }, json={ "from": FROM, "to": email, "subject": "Welcome to Acme", "markdown": "Thanks for signing up.", }, timeout=10, ) if not response.ok: print("Lumail send failed", response.status_code, response.text) return None return response.json()["id"]

4. Lumail MCP server and agent plugins

Replit 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

  • Works in the workspace, fails when published. The deployment needs the secrets too. Check them in the deployment settings, then redeploy.
  • Printing the key while debugging. Agents like to log environment variables to prove they are set. Log whether the key exists, never its value, and rotate it if it shows up in a shared log.
  • 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

Where do I store the Lumail API key on Replit?

In the Secrets tool, as LUMAIL_API_KEY. Your code reads it as an environment variable. Never paste it into a source file, especially in a public Repl.

Is there a Lumail SDK for Python?

No. The official SDK is the lumail npm package. From Python, call POST https://lumail.io/api/v2/emails with requests or httpx and a Bearer token; the JSON body uses the same fields as the SDK.

Why does email work in the workspace but not in my deployment?

The deployment reads its own configuration. Confirm LUMAIL_API_KEY and LUMAIL_FROM are available to the deployment, then redeploy.

Can Replit Agent use the Lumail MCP server?

Replit runs in the cloud, so connect the MCP server to a local agent such as Claude Code, Codex, ChatGPT or Cursor. Your Replit app keeps sending through the API.

How do I test without emailing real people?

Send to [email protected] or any .test domain. Lumail runs the full send path and returns an id, but never delivers the message.

Keep building

Ship email from your Replit app today.

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