Skip to main content

Overview

urBackend Mail Platform extends transactional sending into a full delivery workflow with:
  • async queue-backed single sending (/api/mail/send), with optional replyTo
  • direct provider batch sending (/api/mail/send-batch, 1..100 items, per-item replyTo)
  • 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
Implementation references:

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

  1. Open Project Settings for your project.
  2. Set resendApiKey with your Resend key (re_...).
  3. Optionally set resendFromEmail.
  4. 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.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

Validation notes:
  • to and replyTo must 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 html or text value; 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:
Each item supports: 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 with Max 100 emails per batch.
  • Each to and replyTo value 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 html or text (Provide html or text).
  • Templates (templateId / templateName / variables) are not supported in batch mode. Batch items always go directly to Resend with the exact subject / html / text you 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

  • 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
Dashboard API variant (bearer auth):

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)
Dashboard API live check:

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/audiences
  • POST /api/mail/audiences body: { "name": "VIP Customers" }
  • DELETE /api/mail/audiences/:audienceId

Contacts endpoints

  • 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
For the remaining contact endpoints:

Marketing broadcasts (BYOK)

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

  1. Create broadcast (draft/scheduled payload)
  2. Send broadcast by id

from resolution order

  1. from from request body
  2. project.resendFromEmail
  3. EMAIL_FROM env 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.
Other broadcast endpoints:

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

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/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

Most endpoints follow urBackend envelope shape:
Webhook signature failures currently return:

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-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)
For release-level change history, see May 2026 changelog.