OTP email API
Send one-time passcodes (OTP) for login, transaction confirmation, and step-up authentication with a single Mailbot API call.
Answer first: to send an OTP email, POST https://api.mailbot.id/v1/send with your Bearer key and a JSON body where subject states the code's purpose and text/html contains the generated code. Generate and store the code (with a short expiry) in your own backend — Mailbot delivers the message, it does not generate or verify codes.
Use case
An OTP (one-time passcode) email delivers a short numeric or alphanumeric code that the user types back into your app to prove control of their email address. Common for passwordless login, two-factor authentication, and confirming sensitive actions like payments or profile changes.
Your backend owns the security logic: generate the code, hash and store it with an expiry and attempt limit, and verify it on submission. Mailbot accepts the email payload and returns a message id you can log with the OTP request.
When to send
- A user requests a login code or one-time sign-in link.
- A second factor is required during authentication (2FA/step-up).
- A sensitive action needs confirmation (payout, password change, new device).
API call
curl https://api.mailbot.id/v1/send \
-H "Authorization: Bearer $MAILBOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "noreply@yourdomain.com",
"to": "user@example.com",
"subject": "Your login code: 884921",
"text": "Your one-time code is 884921. It expires in 10 minutes.",
"idempotency_key": "otp-user-142-1718000000"
}'
const code = generateOtp(); // your code generator
await storeOtp(user.id, code, 600); // hash + store, 10-min expiry
const res = await fetch("https://api.mailbot.id/v1/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.MAILBOT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "noreply@yourdomain.com",
to: user.email,
subject: `Your login code: ${code}`,
text: `Your one-time code is ${code}. It expires in 10 minutes.`,
idempotency_key: `otp-${user.id}-${loginAttemptId}`,
}),
});
if (!res.ok) throw new Error("OTP email failed");
Required fields
| Field | Required | Notes |
|---|---|---|
to | Yes | The user's email address. |
subject | Yes | State the purpose; including the code can improve UX but exposes it in notifications. |
text or html | Yes | The body containing the code and its expiry. |
from | No | Verified sender; defaults to the configured sender if omitted. |
idempotency_key | Recommended | Tie to the login attempt so retries don't double-send. |
Example payload
{
"from": "noreply@yourdomain.com",
"to": "user@example.com",
"subject": "Your login code: 884921",
"text": "Your one-time code is 884921. It expires in 10 minutes. If you didn't request this, ignore this email.",
"html": "<p>Your one-time code is <strong>884921</strong>.</p><p>It expires in 10 minutes.</p>",
"idempotency_key": "otp-user-142-1718000000"
}
Example response
{
"ok": true,
"id": "msg_3f8c1a...",
"status": "queued"
}
For delivered messages, the response can include status: "sent" and a delivery_id. See the API reference for every response shape.
Errors
| Status | Meaning | What to do |
|---|---|---|
400 | Invalid payload (bad to, missing subject/body). | Fix the request; read details. |
401 | Bad or missing API key. | Check the Bearer header. |
429 | Send limit reached. | Back off; surface a "try again later" message to the user. |
502 / 503 | Mailbot is temporarily unable to accept the send. | Retry with backoff using the same idempotency_key. |
Full error handling guidance is in the integration guide.
Testing
For safe setup, use the standard test endpoint to confirm delivery reaches an address you control:
curl https://api.mailbot.id/v1/test-email \
-H "Authorization: Bearer $MAILBOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "to": "safe@yourdomain.com", "label": "OTP test" }'
Production checklist
- Codes generated, hashed, stored with a short expiry, and attempt-capped in your backend.
- OTP requests throttled per user and per IP.
idempotency_keytied to the login attempt.- Verified sender domain;
fromset to a verified address. - API key kept server-side; never sent to the browser or mobile client.
429/5xxhandled with backoff and a clear user message.