Migrate App Sending from SMTP to an Email API (2026) with Rovela
Move application transactional sending off Nodemailer, PHPMailer, or smtplib SMTP to Rovela POST /emails — without confusing that path with mailbox SMTP.
Move application sending off SMTP, onto an HTTP email API
If your app still sends transactional mail through SMTP — Nodemailer createTransport({ host, port, auth }), PHPMailer isSMTP(), Python smtplib, Jakarta Mail — a migration to an email API means those jobs call POST /emails instead of talking to a relay. You keep the same triggers (sign-up, reset, receipt). You change the transport, the credentials, and how you observe delivery.
This is an application migration. It is not Email API vs SMTP (which transport to choose), not SMTP Ports 25, 465, and 587 (what 25 / 465 / 587 are for), and not Move Company Email to Rovela (moving people’s mailboxes). For the HTTP contract itself see HTTP Email API.
Start from the Email API product page, the Quick Start, the Send Email API, and Authentication. Also read the SMTP product page so you do not migrate in the wrong direction.
Related product guides in this series:
- Transactional Email API
- HTTP 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
- Send Transactional Email from Your App
- Verified Domain Email API
What you are migrating — and what you are not
Migrate: background workers, request handlers, and cron jobs that currently open an SMTP session to Gmail, SES, a raw Postfix box, or “the SMTP credentials the last vendor gave us,” in order to send OTPs, receipts, and alerts.
Do not migrate to mailbox SMTP. Rovela’s SMTP is for business mailboxes: mx.rovela.dev on port 465 (implicit TLS 1.2+), username = the full mailbox address, password = a device credential created for that client. A mailbox can send only as itself. Limits documented on the SMTP page are 50 recipients per message and 30 messages a minute from one client address. The same page recommends the HTTP API for programmatic sending at volume, because that path adds open/click tracking, logs, and webhooks. Mail apps (Outlook, Apple Mail, Thunderbird) should stay on mailbox SMTP/IMAP; your app should move to HTTP.
A practical cutover
1. Stand up the API next to SMTP
Create a Rovela account, verify the sending domain you already use in MAIL FROM, and issue a sending_access key (Authentication). Put ROVELA_API_KEY and MAIL_FROM in the same server-only secret store you used for the SMTP password. Do not ship the key to a browser.
Keep the old SMTP path behind a flag (EMAIL_TRANSPORT=smtp|api) so you can send a shadow copy or flip one environment at a time.
2. Replace the send function, not every call site
Centralize “send transactional email” in one module — this is also the work in Email API Integration. Call sites pass to, subject, html, text, and a stable idempotency key for that user action (password-reset:${userId}:${tokenId}). The module does the HTTP POST:
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
Map SMTP habits to API fields:
| You used to… | Now |
|---|---|
From: header | from (must be on a verified domain) |
To: / Cc: / Bcc: | to / cc / bcc (combined cap of 50; billed per recipient) |
Reply-To: | reply_to |
| MIME multipart | html and text together, or a stored template |
| MIME attachments | attachments[] with base64 content |
| Library retry / queue ack | Retry the HTTP call with the same Idempotency-Key (Idempotency Keys for Email APIs) |
Date in the future via a scheduler | Rovela scheduled_at, or keep your scheduler and POST at fire time (Scheduled Email Sending) |
3. Change how you learn about bounces
SMTP often means scraping a bounce mailbox or parsing DSNs. On the API, a 200 is only “queued.” Subscribe to email.bounced, email.complained, and email.failed (Transactional Email Webhooks, Handling bounces) and verify signatures (Verifying Webhook Signatures).
4. Flip traffic and retire the relay credentials
Send a canary (your own inbox) in staging, then one production workflow (for example, only password resets), then the rest. Revoke the old SMTP user when the flag is gone. Rotate the Rovela key with create-then-revoke if it was ever logged.
What usually breaks
- Unverified
fromor a new subdomain. SES/Postfix may have allowed a domain you never added in Rovela. You get a400naming the domain until Verified Domain Email API is done. - More than 50 recipients on one message. That is a transport cap, checked before accept (Send Email API). Split the list or use Batch Transactional Email API / broadcasts as appropriate.
- Assuming
200means delivered. Wire webhooks before you delete SMTP bounce handling. - Putting the API key where the SMTP password used to leak (frontend, mobile, CI logs).
sending_accessis enough to send as your domain. - Using Rovela mailbox credentials in Nodemailer “because SMTP still works.” It will send only as that mailbox, at mailbox rates, without the API’s programmatic tooling. That is not this migration.
FAQ
Can we keep SMTP for the app and only add Rovela for new features? You can briefly, behind a flag. The end state should be one transport so retries, keys, and bounce handling do not fork.
What if a vendor library can only speak SMTP? Put a tiny internal service in front of POST /emails, or replace that library’s transport. Almost every modern runtime can make an HTTPS request (A Simple Transactional Email API).
Do we need to redo DKIM? You will publish the DNS records Rovela shows for the sending domain (SPF, DKIM, DMARC, return-path). That is required even if the old relay had its own records (Domain setup).
Is this the same as moving staff inboxes? No. Mailboxes, webmail, and IMAP are Transactional Email API + Hosted Mailboxes. This post is only app sending.
Cut the SMTP socket, keep the product emails
Leave mail apps on mailbox SMTP if people need an inbox. Move application sends to POST /emails with a server-only sending_access key and an Idempotency-Key per action. That is the SMTP-to-API migration Rovela is designed for.