TanStack Start
Updated
Send emails from TanStack Start with Lumail
Lumail itself runs on TanStack Start. Use a server function when your own UI triggers the email, and a server route when something external calls in. Both run only on the server, so the API key never reaches the browser.
TL;DR
npm install lumail, set LUMAIL_API_KEY in .env (no VITE_ prefix), create a client module, then call lumail.emails.send inside createServerFn(...).handler() or a route's server.handlers.POST. Check the returned error before reading data.id.Prerequisites
- A TanStack Start app on a recent 1.x release.
- A Lumail API token that starts with
lum_. - A sending domain verified in the same Lumail organization as the token.
1. Install the packages
2. Set your environment variables
TanStack Start runs on Vite. Variables prefixed with VITE_ are inlined into the client bundle, so the token must not use that prefix. Read it with process.env on the server.
Set the same variable in your host's environment for production builds.
3. Create the client module
Import this module only from server functions and server routes. The Start compiler removes server function bodies from the client build, so the import stays on the server.
4. Send from a server function
createServerFn gives you a typed RPC: validate the input, send, return a serializable result. Throwing inside the handler rejects the promise on the client, which pairs well with TanStack Query mutations.
The validator here uses Zod 4. Any function that returns the parsed input works too.
5. Or expose a server route
Server routes are plain HTTP handlers on a file route. Use them for webhooks, cron callers or other services. Handlers receive the standard Request and return a Response.
6. Test with a fixture address
Call the server function with [email protected]. Lumail runs the full send path, domain check included, and returns an id without delivering anything. Switch to your own inbox for one real send, then check it with the mail tester.
Handle errors
The SDK resolves to { data, error } and does not throw on API errors. In a server function, decide what the client should see: throw a generic error for the UI and log error.name and error.message on the server.
Never forward the raw Lumail error to the browser in a public form. It can mention your domain configuration.
| Status | error.name | What to do |
|---|---|---|
| 401 | missing_api_key | Missing or invalid token. Check the env var on the server that sends. |
| 403 | missing_permission | The token lacks the emails permission. Mint one that has it. |
| 402 | plan limit | The organization used its plan's email volume. Upgrade or wait for the next period. |
| 4xx | validation_error | Missing field, more than one body format, or a from domain that is not verified. |
| 429 | RECIPIENT_RATE_LIMITED | That mailbox hit 5 sends in 10 minutes or 20 in 24 hours. Honor Retry-After. |
| none | network_error / timeout | The request never got an answer. Safe to retry with the same idempotency key. |
Make retries safe with idempotency
Mutations can be retried by TanStack Query, by a user double-clicking, or by your own code. Pass { idempotencyKey } as the second argument so a repeated call returns the already-queued email.
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.
- Email domains - Add a domain and the SPF, DKIM and DMARC records.
- Add a DMARC record - Publish a policy, then tighten it safely.
- Mail tester - Send a real email and check authentication and spam signals.
Production checklist
- No environment variable holding the token starts with
VITE_. src/lib/lumail.tsis imported only from server functions and server routes.- The
fromaddress is on a domain verified in the same organization as the token, with SPF, DKIM and a DMARC record. LUMAIL_API_KEYis set on every environment that sends, never committed, never in a public or client-side variable. Use one token per environment.- Every send that can be retried (webhooks, queues, jobs) passes an idempotency key derived from the triggering event.
- One-time codes and magic links set
tracking: { links: false, open: false }so links are not rewritten. - End-to-end tests send to fixture addresses such as
[email protected], which return an id without sending real email. - A failed email is logged with
error.nameand never fails the user's request on its own.
Frequently asked questions
Server function or server route: which one should send the email?
Use a server function when your own components trigger the email, because you get typed input and output with no fetch boilerplate. Use a server route when an external system calls you, such as Stripe, a cron service or another backend.
Why not use a VITE_ environment variable for the API key?
Vite inlines every VITE_ variable into the client bundle, so anyone could read the key in the browser and send email from your domain. Keep the name LUMAIL_API_KEY and read it with process.env on the server.
Does the Lumail SDK work on every TanStack Start deployment target?
The SDK only uses fetch and standard Web APIs to send, so it runs on Node.js servers and serverless targets. Node.js 20 or later is the tested baseline.
Can I render React Email templates in a server function?
Yes. Call render from @react-email/render inside the handler and pass the HTML string to lumail.emails.send. The React Email guide shows the full template and plain-text setup.
Is Lumail built with TanStack Start?
Yes. The Lumail app runs on TanStack Start, which is why this guide follows the same server function and server route patterns used in production.
Keep building
- GuideSend emails from Next.js with LumailApp Router route handlers and server actions with the lumail SDK.
- GuideSend emails from Node.js with LumailA script, an Express route, batch sends and a safe retry helper.
- GuideSend emails from Python with LumailCall the REST API with requests or httpx, from scripts, Flask or FastAPI.
- DocsSDK: lumail.emails.sendSignature, options and the result shape.
- DocsSend Email API referenceEvery field, option and error for POST /api/v2/emails.
- DocsTest-mode recipientsFixture addresses that never send real email.
- ProductIn-app integrationLet your coding agent wire Lumail into your codebase.
- DocsLumail docsAPI reference, SDK and tutorials.
- Free toolMail testerCheck SPF, DKIM, DMARC and spam signals on a real send.
Send your first email from code today.
3,000 emails a month free. Transactional and marketing email on one verified domain, with unlimited subscribers on every plan.