Overview
urBackend Mail Platform extends transactional sending into a full delivery workflow with:- async queue-backed single sending (
/api/mail/send), with optionalreplyTo - direct provider batch sending (
/api/mail/send-batch, 1..100 items, per-itemreplyTo) - BYOK (Bring Your Own Key) with encrypted project-level Resend keys
- delivery tracking via persistent
MailLog - audience/contact management (BYOK-gated)
- marketing broadcasts (BYOK-gated, counted against the monthly mail quota)
- webhook-driven status updates with Svix verification
How Mail Platform fits urBackend
- 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
BYOK is available on every plan, including Free. Audiences, contacts, and broadcasts only require a configured BYOK Resend key. They do not require a Pro plan.
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
Configure BYOK in Dashboard
- Open Project Settings for your project.
- Set
resendApiKeywith your Resend key (re_...). - Optionally set
resendFromEmail. - Save. urBackend stores this key encrypted at rest.
Required server environment
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.sentemail.deliveredemail.bouncedemail.complained
Sending emails
POST /api/mail/send (single)
Request schema
Validation notes:
toandreplyTomust be valid email addresses. Array forms cannot be empty (Recipient list cannot be empty,Reply-to list cannot be empty).- Subject must be non-empty after any template/variable interpolation (
Subject is required). - Rendered body must contain a non-empty
htmlortextvalue; requests where both are empty after interpolation are rejected (Provide at least one of html or text content.).
Quota and limits
- per-request monthly quota slot is reserved in Redis
- on terminal failure, slot is refunded
- over-limit returns HTTP
429
Example
Success envelope
POST /api/mail/send-batch (max 100)
Request schema
Array of 1..100 items:
Validation is enforced by a strict Zod schema on every batch call:
- The batch array itself must contain 1..100 items. Empty batches are rejected with
Batch cannot be empty; larger batches are rejected withMax 100 emails per batch. - Each
toandreplyTovalue is validated as a proper email address. - Each item must supply a non-empty
subject(Subject is required). - Each item must supply at least one of
htmlortext(Provide html or text). - Templates (
templateId/templateName/variables) are not supported in batch mode. Batch items always go directly to Resend with the exactsubject/html/textyou provide.
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
Mail logs
GET /api/mail/logs (public) and GET /api/projects/:projectId/mail/logs (dashboard)
MailLog fields
resendEmailIdtosubjectstatususingByoktemplateUsedsentAt
queued | sent | delivered | bounced | complained | failed
Sorting/pagination behavior:
- current implementation returns latest 50, sorted by
sentAt DESC
Live status
- Public:
GET /api/mail/logs/:resendId - Dashboard:
GET /api/projects/:projectId/mail/logs/:resendId/live
404if log entry does not belong to the current project (cross-project isolation)
Audiences & Contacts (BYOK-gated)
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/audiencesPOST /api/mail/audiencesbody:{ "name": "VIP Customers" }DELETE /api/mail/audiences/:audienceId
Contacts endpoints
GET /api/mail/audiences/:audienceId/contactsPOST /api/mail/audiences/:audienceId/contactsPATCH /api/mail/audiences/:audienceId/contacts/:contactIdDELETE /api/mail/audiences/:audienceId/contacts/:contactId
email, firstName, lastName, unsubscribed
Marketing broadcasts (BYOK)
Endpoints:POST /api/mail/broadcastsPOST /api/mail/broadcasts/:id/sendGET /api/mail/broadcastsGET /api/mail/broadcasts/:idDELETE /api/mail/broadcasts/:id
Two-step flow
- Create broadcast (draft/scheduled payload)
- Send broadcast by id
from resolution order
fromfrom request bodyproject.resendFromEmailEMAIL_FROMenv fallback
Quota checks
Broadcasts require a configured BYOK Resend key on the project. They are available on both the Free and Pro plans. Each broadcast send reserves one slot from the project’s shared monthly mail quota (50 on Free, 1,000 on Pro), the same quota used by transactional sends. If Resend rejects the send, urBackend refunds the reserved slot so failed sends do not count against your quota.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
SetRESEND_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
200with{"success":true,"message":"Webhook ignored: secret not configured."}and skips verification/processing.
Event mapping to MailLog.status
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.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
Audiences & Contacts tab
- 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 if BYOK key is not configured
- each send counts against the monthly mail quota
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/decrypthelpers (AES) - rotate/clear BYOK through project update workflows (
PATCH /api/projects/:projectIdwithresendApiKey: null, then set a newre_...key) - live status endpoints validate project ownership before provider lookups
- webhook authenticity is checked with Svix verification
Error reference
Most endpoints follow urBackend envelope shape:
Changelog & migration notes
Compared to legacy single-send usage:/api/mail/sendis now asynchronous queue-backed/api/mail/send-batchadds bulk dispatch (up to 100 items/request)MailLogis first-class for auditability and dashboard visibility- new BYOK-gated resources: audiences, contacts
- new BYOK-gated resource: broadcasts (counted against the monthly mail quota)
- webhook path requires raw-body middleware placement before JSON parser
- new env dependencies for production-grade mail processing (
RESEND_WEBHOOK_SECRET, sender defaults)
