Send Transactional Email from Your App (2026) with the Rovela API
Hook sign-up, checkout, and password-reset events in your app to Rovela POST /emails: server-only keys, per-action Idempotency-Key, and HTML plus text bodies.
Send transactional email from your application
When a user signs up, checks out, or asks for a password reset, your application should send the mail — from a server-side handler, with a secret that never ships to the browser. This post hooks those product events to Rovela’s POST /emails. The architectural wrapper is Email API Integration; the “what is a transactional API” pillar is Transactional Email API. Language encyclopedias already exist (How to Send Email in Node.js, How to Send Email with Python, Sending OTP Codes by Email).
Start from the Email API product page, the Quick Start, the Send Email API, and Authentication. Official recipes: verification code, password reset, invoice attachment.
Related product guides in this series:
- Transactional Email API
- HTTP Email API
- Migrate SMTP to an Email API
- Email API for SaaS Apps
- Send Email with curl
- Transactional Email API + Hosted Mailboxes
- Inbound and Outbound Email API
- Email Sending API
- Email Notification API
- Email API Integration
- Batch Transactional Email API
- Transactional Email Webhooks
- A Simple Transactional Email API
- Verified Domain Email API
Where the call lives
Put the send in the same trusted process that just committed the user action:
- After you persist a hashed OTP, send the code (example, Sending OTP Codes by Email).
- After you store a reset token, send the link (example, Secure Password Reset Emails).
- After a successful charge, send the receipt, optionally with a PDF attachment (example, Send an Invoice Email with a PDF Attachment).
- After you create an invite, send the accept URL (Team Invite Email Links).
Do not call Rovela from the SPA or the mobile app. The browser gets “we sent a code”; the server talks to the API (Authentication).
A handler-sized example
const res = await fetch("https://api.rovela.dev/emails", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.ROVELA_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": `signup-verify:${userId}:${codeId}`,
},
body: JSON.stringify({
from: process.env.MAIL_FROM, // e.g. "Acme <no-reply@yourdomain.com>"
to: user.email,
subject: `Your Acme code: ${code}`,
html: `<p>Your verification code is <b>${code}</b>. It expires in 10 minutes.</p>`,
text: `Your verification code is ${code}. It expires in 10 minutes.`,
tags: { kind: "signup-otp", userId },
}),
});
if (!res.ok) {
const err = await res.json();
throw new Error(`Rovela ${res.status}: ${err.message}`);
}
const { id } = await res.json(); // accepted and queued — not yet delivered
Checklist for every handler:
MAIL_FROMis on a Verified Domain Email API.ROVELA_API_KEYissending_access, from the environment.Idempotency-Keyis stable for this action (password-reset:${userId}:${tokenId}), notDate.now().- You send
htmlandtext(the code in the subject plus the text body is what people see on a watch or a lock screen). - You log the returned
id, never the API key. A200is queued, not delivered. - Failures bubble to your error tracker; a timeout retries with the same key.
Welcome copy that marketing will edit belongs in a stored template (Welcome Email from a Template). Secrets (codes, tokens) stay generated in the app.
Hooking common frameworks (the same contract)
Whether you are in Express, Django, Rails, Laravel, or a serverless function, the steps are identical: environment variables on the server, one wrapper, one POST. Existing framework posts on this blog (Next.js, FastAPI, Laravel OTP, and others) all use this contract. If you are coming from SMTP in that framework, Migrate SMTP to an Email API is the migration path.
For many personalized follow-ups in one job (trial reminders), switch that job to Batch Transactional Email API. For “send this exact body at 09:00,” Scheduled Email Sending. For “the system crossed a threshold,” Email Notification API.
After the user is mailed
Subscribe to email.bounced and email.complained so a bad address does not stay in your retry loop (Transactional Email Webhooks). If the user should be able to reply to a human, set reply_to to a hosted mailbox (Transactional Email API + Hosted Mailboxes).
FAQ
The user double-clicked “send code.” If both requests share the same Idempotency-Key (same codeId), they get one message. If you minted two codes, you minted two keys — prevent the second issue at the application layer (rate limits, reuse the outstanding code).
Can I send from a queue worker instead of the request handler? Yes, and you often should, as long as the worker has sending_access and the idempotency key is created when the action is accepted, not when the worker happens to run.
What about GraphQL or tRPC? They are still server procedures. Reuse the wrapper; there are already dedicated posts for those gates on the blog.
First time wiring anything? Follow Send Your First Email in 5 Minutes or the first email guide once, then come back and attach it to a real user event.
Call the API where the event happens
The application already knows the user acted. Keep the key and the send next to that action, give the request a stable idempotency key, and let Rovela deliver. That is all “send transactional email from your app” has to mean.