anolilab/lunora/lunora-setup-mail
Claude Code and agent skill by anolilab/lunora
- community
Checking that the SKILL.md file is still in the repository.
Adds transactional email to a Lunora app with the `mail` registry item (`@lunora/mail`). Covers the server-only `internal.mail.sendEmail` / `queueEmail` actions, the `SEND_EMAIL` Cloudflare Email Workers binding, Resend via `RESEND_API_KEY`, `react-email` templates via `renderEmail`, the dev mail catcher (Studio Mail tab), E2E inbox helpers, and inbound mail. Use when the user wants to send verification, password-reset, invite or notification email, runs `lunora add email` or `lunora registry a…
Install
npx vskill@latest install anolilab/lunora/lunora-setup-mailSKILL.md
Lunora Setup Mail
The mail registry item wraps @lunora/mail, which validates addresses against header injection before sending. It exposes two internalActions, sendEmail and queueEmail. Both are server-only on purpose: a mailer that lets the client pick the recipient, subject and body is an open relay for phishing through your verified domain. In dev, every send is captured into the Studio Mail tab and nothing is delivered.
If the project has no Lunora backend yet, start with lunora-quickstart.
Step 1: Add the item
Then run pnpm install and lunora codegen. The item does the following:
- Adds
@lunora/mailand@lunora/server. - Copies
lunora/mail/index.tsinto the project. The project owns this file. It definessendEmail, which returns{ id }, andqueueEmail, which returns{ queued: true }. - Adds a
send_emailbinding namedSEND_EMAIL(withdestination_address) towrangler.jsonc. - Writes
MAIL_FROMto.dev.vars.
Codegen emits the two functions as internal.mail.sendEmail and internal.mail.queueEmail. They are not in the client-reachable api. A client call to them returns FUNCTION_NOT_FOUND.
Step 2: Choose the delivery path
The copied file builds its mailer with createMailerFromEnv. That function picks the transport in this order:
| Condition | Transport | |
|---|---|---|
LUNORA_MAIL_CAPTURE=1, or a dev environment (below) | Capture into the Studio Mail tab | |
SEND_EMAIL binding present | Cloudflare Email Workers | |
no binding, RESEND_API_KEY secret set | Resend | |
| none of the above | throws on send (production never captures silently) |
- Cloudflare Email Workers. The binding sends to a single recipient, and only to verified Email Routing destinations. That fits app-to-operator mail. Verify the address and replace the placeholder.
- Resend. Use this to send to arbitrary users. Run
wrangler secret put RESEND_API_KEYand remove thesend_emailbinding. While the binding exists it takes precedence over Resend. - Capture. Capture turns on when every env-name var (
WORKER_ENV,NODE_ENV, …) looks like dev.lunora devsetsWORKER_ENV=development, so capture is on in dev. Capture writes to the root shard. IfSHARDorLUNORA_ADMIN_TOKENis missing, captured mail is discarded and a one-time warning is logged. To deliver for real from dev, setLUNORA_MAIL_CAPTURE=0.
MAIL_FROM is required on every path. It accepts Name <addr@host> or a bare address.
Step 3: Send from a server function
sendEmail is an action because sending is non-transactional network I/O. From a mutation, schedule it so the send runs only after the mutation commits:
Sending to an arbitrary args.email needs the Resend path in production: the SEND_EMAIL binding only reaches its verified destination.
From an action, call await ctx.runAction(internal.mail.sendEmail, { … }).
If a client needs to trigger a send, write a public action for that one purpose. It should take only business inputs (for example { orderId }), check ctx.auth, derive the recipient on the server, rate-limit the call (@lunora/ratelimit), and then call internal.mail.sendEmail.
React templates
A React element can't be serialized across the RPC boundary, so render it to html and text first. Rendering this way keeps dev capture working:
For the auth flows, lunora add auth-emails adds ready-made templates.
Queueing (optional)
queueEmail requires a Cloudflare Queue producer binding passed as createMailerFromEnv(env, { queue }) in mailer(), and the item doesn't add one. Without it, the call throws ` queue binding is required for mailer.queue() , except under capture, where it sends straight to the capture inbox so dev flows keep working. A consumer builds its mailer with createMailerFromEnv too, drains the queue with consumeQueuedSend(mailer, message.body) and then calls message.ack() for each message. On a throw, branch on isRetryableMailError(error): retry with message.retry()` when it is true, and ack and log when it is false, since a refused address or malformed body never sends and a retry only dead-letters it. With a queue bound in dev, the message still goes through the queue and the consumer's mailer captures it into the inbox. Queues deliver at least once: with per-message acks a later throw retries only the unacknowledged messages, while without them a single throw resends the whole batch. The full wiring is in the "Queueing" section of the item README.
Testing and inbound
- E2E.
@lunora/mail/testingexportswaitForMail({ baseUrl, adminToken, to, subjectMatch })andextractLink(mail, { match }). Together they let you read the captured inbox and pull out a verification or reset link. - Receiving mail.
@lunora/mail/inboundprovidescreateInboundEmailHandler,parseInboundEmailanddispatchToLunoraFunction. They route Email Routing messages into a Lunora function. Mount them with the generated builder's.onEmail(...).
Verify
- Run
lunora devand trigger a send. WithSHARDandLUNORA_ADMIN_TOKENset, the message should appear in the Studio Mail tab; if either is missing, capture discards it and logs a warning. - Before deploying, run
lunora doctor. It flags the placeholder destination. Confirm thatMAIL_FROMis set, and that either the binding uses a verified destination orRESEND_API_KEYis set with the binding removed.
Security
- Security scanPassed, 96 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingpackages/cli/skills/lunora-setup-mail/SKILL.md
Popularity
- Repository stars
- 291
- Repository forks
- 14
- 7-day trend score
- 5
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/anolilab/lunora/lunora-setup-mail)