Documentation Index Fetch the complete documentation index at: https://docs.ub.bitbros.in/llms.txt
Use this file to discover all available pages before exploring further.
Overview
urBackend Mail Platform extends transactional sending into a full delivery workflow with:
async queue-backed single sending (/api/mail/send)
direct provider batch sending (/api/mail/send-batch)
BYOK (Bring Your Own Key) with encrypted project-level Resend keys
delivery tracking via persistent MailLog
audience/contact management (BYOK-gated)
marketing broadcasts (BYOK + Pro gated)
webhook-driven status updates with Svix verification
Implementation references:
Public API (/api/mail/*): app/runtime sending, logs, live status, webhook receiver.
Dashboard API (/api/projects/:projectId/mail/*): operator/admin workflows (logs, live checks, audiences, contacts, broadcasts).
Dashboard UI (/project/:projectId/mail): unified Mail Platform control plane.
Architecture
Feature gating matrix
Capability Free (shared key) Free + BYOK Pro + BYOK Transactional send (/api/mail/send) ✅ ✅ ✅ Batch send (/api/mail/send-batch, max 100 items) ✅ ✅ ✅ Delivery logs + live status ✅ ✅ ✅ Audiences & Contacts ❌ ✅ ✅ Marketing Broadcasts ❌ ❌ ✅
Getting started
Prerequisites
Resend account
Valid Resend key with format: re_[A-Za-z0-9_]+
urBackend project with a Secret Key (sk_live_...) for public API calls
Open Project Settings for your project.
Set resendApiKey with your Resend key (re_...).
Optionally set resendFromEmail.
Save. urBackend stores this key encrypted at rest.
Required server environment
Variable Required Purpose RESEND_API_KEY✅ (fallback) Default/shared provider key RESEND_API_KEY_2Optional Higher-priority fallback key RESEND_WEBHOOK_SECRET✅ for webhook verification Svix secret for /api/mail/webhook EMAIL_FROM✅ recommended Default sender fallback
Register webhook in Resend
In Resend dashboard, configure webhook URL:
POST https://<your-public-api-domain>/api/mail/webhook
Enable relevant events at minimum:
email.sent
email.delivered
email.bounced
email.complained
Sending emails
All /api/mail/* send endpoints require your Secret Key (sk_live_...) in x-api-key.
POST /api/mail/send (single)
Request schema
Field Type Required Notes tostring✅ recipient email subjectstringDirect mode ✅ required when not using template fields htmlstringDirect mode one of html/text HTML body textstringDirect mode one of html/text Text body templateIdstringTemplate mode one of templateId/templateName 24-char ObjectId templateNamestringTemplate mode one of templateId/templateName key/name lookup variablesobjectOptional template vars
Quota and limits
per-request monthly quota slot is reserved in Redis
on terminal failure, slot is refunded
over-limit returns HTTP 429
Example
curl -X POST "https://api.ub.bitbros.in/api/mail/send" \
-H "Content-Type: application/json" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY" \
-d '{
"to": "user@example.com",
"subject": "Welcome",
"html": "<h1>Hello</h1>"
}'
Success envelope
{
"success" : true ,
"data" : {
"id" : "<queue-job-id>" ,
"provider" : "byok" ,
"monthlyUsage" : 12 ,
"monthlyLimit" : 100
},
"message" : "Mail queued successfully."
}
POST /api/mail/send-batch (max 100)
Request schema
Array of 1..100 items:
[
{
"to" : "u1@example.com" ,
"subject" : "Campaign" ,
"html" : "<p>Hello</p>"
}
]
Each item supports to, subject, html?, text?.
Quota behavior
reserves one quota slot per batch item before provider call
if provider call fails, each reserved slot is refunded
response includes per-item provider result objects
Partial success
data returns per-recipient/provider results. Treat each item independently in your caller logic.
Example
curl -X POST "https://api.ub.bitbros.in/api/mail/send-batch" \
-H "Content-Type: application/json" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY" \
-d '[
{"to":"a@example.com","subject":"Hi A","html":"<p>A</p>"},
{"to":"b@example.com","subject":"Hi B","html":"<p>B</p>"}
]'
Mail logs
GET /api/mail/logs (public) and GET /api/projects/:projectId/mail/logs (dashboard)
MailLog fields
resendEmailId
to
subject
status
usingByok
templateUsed
sentAt
Status enum:
queued | sent | delivered | bounced | complained | failed
Sorting/pagination behavior:
current implementation returns latest 50, sorted by sentAt DESC
curl "https://api.ub.bitbros.in/api/mail/logs" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY"
Dashboard API variant (bearer auth):
curl "https://dashboard-api.ub.bitbros.in/api/projects/<projectId>/mail/logs" \
-H "Authorization: Bearer <dashboard-jwt>"
Live status
Public: GET /api/mail/logs/:resendId
Dashboard: GET /api/projects/:projectId/mail/logs/:resendId/live
Use live status when you need real-time provider status. Use stored logs for analytics/history and low-latency UI lists.
404 if log entry does not belong to the current project (cross-project isolation)
curl "https://api.ub.bitbros.in/api/mail/logs/re_123" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY"
Dashboard API live check:
curl "https://dashboard-api.ub.bitbros.in/api/projects/<projectId>/mail/logs/re_123/live" \
-H "Authorization: Bearer <dashboard-jwt>"
These endpoints proxy Resend API and require a valid BYOK key on the project.
Error status/message semantics follow upstream Resend responses where applicable.
Audiences endpoints
GET /api/mail/audiences
POST /api/mail/audiences body: { "name": "VIP Customers" }
DELETE /api/mail/audiences/:audienceId
curl "https://api.ub.bitbros.in/api/mail/audiences" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY"
curl -X POST "https://api.ub.bitbros.in/api/mail/audiences" \
-H "Content-Type: application/json" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY" \
-d '{"name":"VIP Customers"}'
curl -X DELETE "https://api.ub.bitbros.in/api/mail/audiences/aud_123" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY"
GET /api/mail/audiences/:audienceId/contacts
POST /api/mail/audiences/:audienceId/contacts
PATCH /api/mail/audiences/:audienceId/contacts/:contactId
DELETE /api/mail/audiences/:audienceId/contacts/:contactId
Contact payload fields:
email, firstName, lastName, unsubscribed
curl -X POST "https://api.ub.bitbros.in/api/mail/audiences/aud_123/contacts" \
-H "Content-Type: application/json" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY" \
-d '{
"email": "contact@example.com",
"firstName": "Ada",
"lastName": "Lovelace",
"unsubscribed": false
}'
For the remaining contact endpoints:
curl "https://api.ub.bitbros.in/api/mail/audiences/aud_123/contacts" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY"
curl -X PATCH "https://api.ub.bitbros.in/api/mail/audiences/aud_123/contacts/ct_123" \
-H "Content-Type: application/json" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY" \
-d '{"firstName":"Ada","unsubscribed":true}'
curl -X DELETE "https://api.ub.bitbros.in/api/mail/audiences/aud_123/contacts/ct_123" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY"
Marketing broadcasts (BYOK + Pro)
Endpoints:
POST /api/mail/broadcasts
POST /api/mail/broadcasts/:id/send
GET /api/mail/broadcasts
GET /api/mail/broadcasts/:id
DELETE /api/mail/broadcasts/:id
Two-step flow
Create broadcast (draft/scheduled payload)
Send broadcast by id
from resolution order
from from request body
project.resendFromEmail
EMAIL_FROM env fallback
Quota checks
Broadcast create/send/list/detail/delete paths run behind mail usage gating middleware and plan checks.
# 1) Create
curl -X POST "https://api.ub.bitbros.in/api/mail/broadcasts" \
-H "Content-Type: application/json" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY" \
-d '{
"audienceId": "aud_123",
"subject": "May launch",
"html": "<h1>We shipped</h1>",
"scheduledAt": "2026-05-20T10:00:00.000Z"
}'
# 2) Send
curl -X POST "https://api.ub.bitbros.in/api/mail/broadcasts/brd_123/send" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY"
Other broadcast endpoints:
curl "https://api.ub.bitbros.in/api/mail/broadcasts" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY"
curl "https://api.ub.bitbros.in/api/mail/broadcasts/brd_123" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY"
curl -X DELETE "https://api.ub.bitbros.in/api/mail/broadcasts/brd_123" \
-H "x-api-key: sk_live_YOUR_SECRET_KEY"
Webhook integration
Endpoint: POST /api/mail/webhook
Why raw-body parsing is required
Svix signatures are computed over raw payload bytes. express.raw({ type: 'application/json' }) must run before JSON parser on this path.
Secret configuration
Set RESEND_WEBHOOK_SECRET in server env. Webhook signature verification and event processing only occur when this secret is configured correctly.
Current behavior when missing/misconfigured: the handler returns HTTP 200 with {"success":true,"message":"Webhook ignored: secret not configured."} and skips verification/processing.
Event mapping to MailLog.status
Resend event type MailLog status email.sentsentemail.delivereddeliveredemail.bouncedbouncedemail.complainedcomplainedemail.delivery_delayedqueued
email.sent represents provider acceptance (intermediate state), not final inbox delivery.
Retry behavior
Resend controls webhook retries for non-2xx/timeout outcomes. Keep endpoint idempotent and safe for repeated delivery attempts.
curl
JavaScript (server webhook simulator)
curl -X POST "https://api.ub.bitbros.in/api/mail/webhook" \
-H "Content-Type: application/json" \
-H "svix-id: <id>" \
-H "svix-timestamp: <timestamp>" \
-H "svix-signature: <signature>" \
-d '{"type":"email.delivered","data":{"email_id":"re_123"}}'
Dashboard UI guide
Route: /project/:projectId/mail
Delivery Logs tab
status badge (queued/sent/delivered/bounced/complained/failed)
subject, recipient(s), provider id, sent time
live status modal fetches provider status by resend id
create/delete audiences
add/remove contacts
locked if BYOK key is not configured
Marketing Broadcasts tab
compose audience + subject + html
send campaign via broadcast API
locked unless BYOK is configured and account is Pro
Add product screenshots/annotated walkthrough images in this section if your docs deployment supports hosted image assets.
Security notes
BYOK format validation: ^re_[A-Za-z0-9_]+$
key encryption at rest uses shared encrypt/decrypt helpers (AES)
rotate/clear BYOK through project update workflows (PATCH /api/projects/:projectId with resendApiKey: null, then set a new re_... key)
live status endpoints validate project ownership before provider lookups
webhook authenticity is checked with Svix verification
Error reference
Endpoint Status Typical message POST /api/mail/send400Invalid mail payload / template not found / subject required POST /api/mail/send403Secret key required POST /api/mail/send429Monthly mail limit exceeded POST /api/mail/send-batch400Invalid batch mail payload POST /api/mail/send-batch403Secret key required POST /api/mail/send-batch429Monthly mail limit exceeded GET /api/mail/logs/:resendId400Invalid resendId format GET /api/mail/logs/:resendId404Mail log entry not found for this project GET /api/mail/audiences + audience mutations403This feature requires a BYOK Resend key GET/PATCH/DELETE /api/mail/audiences/:id/contacts/*400Invalid audience/contact id format GET/PATCH/DELETE /api/mail/audiences/:id/contacts/*403Contacts require a custom Resend API Key (BYOK) POST /api/mail/broadcasts*400audienceId, subject, and html are required POST /api/mail/broadcasts*403Broadcasts require both BYOK and Pro plan POST /api/mail/webhook400Webhook signature verification failed Provider-proxied endpoints passthrough Upstream Resend status/message are surfaced
Most endpoints follow urBackend envelope shape:
{
"success" : false ,
"data" : {},
"message" : "Human-readable error"
}
Webhook signature failures currently return:
{
"success" : false ,
"message" : "Webhook signature verification failed."
}
Changelog & migration notes
Compared to legacy single-send usage:
/api/mail/send is now asynchronous queue-backed
/api/mail/send-batch adds bulk dispatch (up to 100 items/request)
MailLog is first-class for auditability and dashboard visibility
new BYOK-gated resources: audiences, contacts
new BYOK+Pro resource: broadcasts
webhook path requires raw-body middleware placement before JSON parser
new env dependencies for production-grade mail processing (RESEND_WEBHOOK_SECRET, sender defaults)
For release-level change history, see May 2026 changelog .