From 5dade6b8f3309af5d5b4021521bc11b1481d585d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 11:53:09 +0000 Subject: [PATCH 1/6] Initial plan From 73af4b409b13c5fc4cb5f2296a21d2f94d8446ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 11:57:58 +0000 Subject: [PATCH 2/6] docs: add comprehensive Mail Platform developer guide Agent-Logs-Url: https://github.com/geturbackend/urBackend/sessions/baf6f95e-1b45-4f0e-9075-37ec86eeb8be Co-authored-by: yash-pouranik <172860064+yash-pouranik@users.noreply.github.com> --- mintlify/docs/api-reference/mail/send.mdx | 4 + mintlify/docs/docs.json | 21 +- mintlify/docs/guides/mail-platform.mdx | 604 ++++++++++++++++++++++ 3 files changed, 619 insertions(+), 10 deletions(-) create mode 100644 mintlify/docs/guides/mail-platform.mdx diff --git a/mintlify/docs/api-reference/mail/send.mdx b/mintlify/docs/api-reference/mail/send.mdx index 4d8910f1..02d01c9a 100644 --- a/mintlify/docs/api-reference/mail/send.mdx +++ b/mintlify/docs/api-reference/mail/send.mdx @@ -7,6 +7,10 @@ description: "Send transactional email with direct content or a named template." Sends transactional email from your project. + + For the complete Mail Platform documentation (BYOK, batch sending, logs, audiences, contacts, broadcasts, webhooks, and dashboard workflows), see Mail Platform guide. + + This endpoint requires a **Secret Key** (`sk_live_...`). Do not call it from browser/client code. diff --git a/mintlify/docs/docs.json b/mintlify/docs/docs.json index f6721d50..3647134e 100644 --- a/mintlify/docs/docs.json +++ b/mintlify/docs/docs.json @@ -44,16 +44,17 @@ "concepts/row-level-security" ] }, - { - "group": "Guides", - "pages": [ - "guides/authentication", - "guides/social-auth", - "guides/database", - "guides/storage", - "guides/webhooks" - ] - }, + { + "group": "Guides", + "pages": [ + "guides/authentication", + "guides/social-auth", + "guides/mail-platform", + "guides/database", + "guides/storage", + "guides/webhooks" + ] + }, { "group": "SDK", "pages": [ diff --git a/mintlify/docs/guides/mail-platform.mdx b/mintlify/docs/guides/mail-platform.mdx new file mode 100644 index 00000000..00338256 --- /dev/null +++ b/mintlify/docs/guides/mail-platform.mdx @@ -0,0 +1,604 @@ +--- +title: "Mail Platform" +description: "Comprehensive developer guide for urBackend Mail Platform: BYOK, batch delivery, logs, audiences, contacts, broadcasts, and webhook processing." +--- + +## Overview + +urBackend Mail Platform extends transactional sending into a full delivery workflow with: + +- async queue-backed sending (`/api/mail/send`, `/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: + +- [Mail Platform implementation PR #172](https://github.com/geturbackend/urBackend/pull/172) +- [Mail security fixes PR #175](https://github.com/geturbackend/urBackend/pull/175) + +### 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 + +```mermaid +flowchart LR + A[Client request\n/api/mail/send or /api/mail/send-batch] --> B[Public API] + B --> C[Quota reservation + BYOK/default provider resolve] + C --> D[BullMQ mail queue] + D --> E[Resend API] + E --> F[Delivery events webhook] + F --> G[POST /api/mail/webhook\nSvix verify] + G --> H[(MailLog)] + H --> I[Dashboard/Public log queries] +``` + +### 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 + +### 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 + +| Variable | Required | Purpose | +|---|---:|---| +| `RESEND_API_KEY` | ✅ (fallback) | Default/shared provider key | +| `RESEND_API_KEY_2` | Optional | 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:///api/mail/webhook` + +Enable relevant events at minimum: + +- `email.delivered` +- `email.bounced` +- `email.complained` +- `email.failed` + +## 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 | +|---|---|---:|---| +| `to` | `string` | ✅ | recipient email | +| `subject` | `string` | Direct mode ✅ | required when not using template fields | +| `html` | `string` | Direct mode one of `html/text` | HTML body | +| `text` | `string` | Direct mode one of `html/text` | Text body | +| `templateId` | `string` | Template mode one of `templateId/templateName` | 24-char ObjectId | +| `templateName` | `string` | Template mode one of `templateId/templateName` | key/name lookup | +| `variables` | `object` | Optional | 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 + + + +```bash curl +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": "

Hello

" + }' +``` + +```ts JavaScript SDK +import { UrBackendClient } from 'urbackend-sdk'; + +const client = new UrBackendClient({ apiKey: process.env.URB_SECRET_KEY! }); +const result = await client.mail.send({ + to: 'user@example.com', + subject: 'Welcome', + html: '

Hello

' +}); +console.log(result.id, result.provider); +``` + +
+ +#### Success envelope + +```json +{ + "success": true, + "data": { + "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: + +```json +[ + { + "to": "u1@example.com", + "subject": "Campaign", + "html": "

Hello

" + } +] +``` + +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 + + + +```bash curl +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":"

A

"}, + {"to":"b@example.com","subject":"Hi B","html":"

B

"} + ]' +``` + +```ts JavaScript SDK +import { UrBackendClient } from 'urbackend-sdk'; + +const client = new UrBackendClient({ apiKey: process.env.URB_SECRET_KEY! }); +const result = await client.request('POST', '/api/mail/send-batch', { + body: [ + { to: 'a@example.com', subject: 'Hi A', html: '

A

' }, + { to: 'b@example.com', subject: 'Hi B', html: '

B

' } + ] +}); +console.log(result); +``` + +
+ +## Mail logs + +### GET `/api/mail/logs` (public) and GET `/: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` + + + +```bash curl +curl "https://api.ub.bitbros.in/api/mail/logs" \ + -H "x-api-key: sk_live_YOUR_SECRET_KEY" +``` + +```ts JavaScript SDK +const logs = await client.request('GET', '/api/mail/logs'); +console.log(logs); +``` + + + +Dashboard API variant (bearer auth): + +```bash +curl "https://dashboard-api.ub.bitbros.in/api/projects//mail/logs" \ + -H "Authorization: Bearer " +``` + +### Live status + +- Public: `GET /api/mail/logs/:resendId` +- Dashboard: `GET /: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) + + + +```bash curl +curl "https://api.ub.bitbros.in/api/mail/logs/re_123" \ + -H "x-api-key: sk_live_YOUR_SECRET_KEY" +``` + +```ts JavaScript SDK +const status = await client.request('GET', '/api/mail/logs/re_123'); +console.log(status.dbLog, status.resendStatus); +``` + + + +Dashboard API live check: + +```bash +curl "https://dashboard-api.ub.bitbros.in/api/projects//mail/logs/re_123/live" \ + -H "Authorization: Bearer " +``` + +## 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` + + + +```bash curl +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" +``` + +```ts JavaScript SDK +await client.request('GET', '/api/mail/audiences'); +await client.request('POST', '/api/mail/audiences', { body: { name: 'VIP Customers' } }); +await client.request('DELETE', '/api/mail/audiences/aud_123'); +``` + + + +### 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` + + + +```bash curl +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 + }' +``` + +```ts JavaScript SDK +const contact = await client.request('POST', '/api/mail/audiences/aud_123/contacts', { + body: { + email: 'contact@example.com', + firstName: 'Ada', + lastName: 'Lovelace', + unsubscribed: false + } +}); +console.log(contact); +``` + + + +For the remaining contact endpoints: + + + +```bash curl +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" +``` + +```ts JavaScript SDK +await client.request('GET', '/api/mail/audiences/aud_123/contacts'); +await client.request('PATCH', '/api/mail/audiences/aud_123/contacts/ct_123', { + body: { firstName: 'Ada', unsubscribed: true } +}); +await client.request('DELETE', '/api/mail/audiences/aud_123/contacts/ct_123'); +``` + + + +## 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 + +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 + +Broadcast create/send/list/detail/delete paths run behind mail usage gating middleware and plan checks. + + + +```bash curl +# 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": "

We shipped

", + "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" +``` + +```ts JavaScript SDK +const created = await client.request('POST', '/api/mail/broadcasts', { + body: { + audienceId: 'aud_123', + subject: 'May launch', + html: '

We shipped

' + } +}); + +await client.request('POST', `/api/mail/broadcasts/${created.id}/send`); +``` + +
+ +Other broadcast endpoints: + + + +```bash curl +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" +``` + +```ts JavaScript SDK +await client.request('GET', '/api/mail/broadcasts'); +await client.request('GET', '/api/mail/broadcasts/brd_123'); +await client.request('DELETE', '/api/mail/broadcasts/brd_123'); +``` + + + +## 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. Without a valid secret, your deployment should fail closed for webhook processing. + +- Fail-closed expectation: return HTTP `500` when secret is missing/misconfigured. + +### Event mapping to `MailLog.status` + +| Resend event type | MailLog status | +|---|---| +| `email.delivered` | `delivered` | +| `email.bounced` | `bounced` | +| `email.complained` | `complained` | +| `email.failed` | `failed` | +| `email.delivery_delayed` | `queued` | + +### Retry behavior + +Resend controls webhook retries for non-2xx/timeout outcomes. Keep endpoint idempotent and safe for repeated delivery attempts. + + + +```bash curl +curl -X POST "https://api.ub.bitbros.in/api/mail/webhook" \ + -H "Content-Type: application/json" \ + -H "svix-id: " \ + -H "svix-timestamp: " \ + -H "svix-signature: " \ + -d '{"type":"email.delivered","data":{"email_id":"re_123"}}' +``` + +```ts JavaScript (server webhook simulator) +await fetch('https://api.ub.bitbros.in/api/mail/webhook', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'svix-id': '', + 'svix-timestamp': '', + 'svix-signature': '' + }, + body: JSON.stringify({ + 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 + +### 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 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/send` | `400` | Invalid mail payload / template not found / subject required | +| `POST /api/mail/send` | `403` | Secret key required | +| `POST /api/mail/send` | `429` | Monthly mail limit exceeded | +| `POST /api/mail/send-batch` | `400` | Invalid batch mail payload | +| `POST /api/mail/send-batch` | `403` | Secret key required | +| `POST /api/mail/send-batch` | `429` | Monthly mail limit exceeded | +| `GET /api/mail/logs/:resendId` | `400` | Invalid resendId format | +| `GET /api/mail/logs/:resendId` | `404` | Mail log entry not found for this project | +| `GET /api/mail/audiences` + audience mutations | `403` | This feature requires a BYOK Resend key | +| `GET/PATCH/DELETE /api/mail/audiences/:id/contacts/*` | `400` | Invalid audience/contact id format | +| `GET/PATCH/DELETE /api/mail/audiences/:id/contacts/*` | `403` | Contacts require a custom Resend API Key (BYOK) | +| `POST /api/mail/broadcasts*` | `400` | audienceId, subject, and html are required | +| `POST /api/mail/broadcasts*` | `403` | Broadcasts require both BYOK and Pro plan | +| `POST /api/mail/webhook` | `400` | Webhook signature verification failed | +| Provider-proxied endpoints | passthrough | Upstream Resend status/message are surfaced | + +All responses follow urBackend envelope shape: + +```json +{ + "success": false, + "data": {}, + "message": "Human-readable error" +} +``` + +## 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](/changelog/may-2026). From ddf4c165d81af69b23c07099a92291e738c2199c Mon Sep 17 00:00:00 2001 From: Yash Pouranik Date: Sun, 17 May 2026 17:35:34 +0530 Subject: [PATCH 3/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- mintlify/docs/guides/mail-platform.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mintlify/docs/guides/mail-platform.mdx b/mintlify/docs/guides/mail-platform.mdx index 00338256..021a87ff 100644 --- a/mintlify/docs/guides/mail-platform.mdx +++ b/mintlify/docs/guides/mail-platform.mdx @@ -259,7 +259,7 @@ curl "https://dashboard-api.ub.bitbros.in/api/projects//mail/logs" \ ### Live status - Public: `GET /api/mail/logs/:resendId` -- Dashboard: `GET /:projectId/mail/logs/:resendId/live` +- 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. From 8768ce1ad9764e97fd0bcd34e15ce829dabcbe31 Mon Sep 17 00:00:00 2001 From: Yash Pouranik Date: Sun, 17 May 2026 17:36:55 +0530 Subject: [PATCH 4/6] docs: fix if webhook secret is not configured RESEND_WEBHOOK_SECRET Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- mintlify/docs/guides/mail-platform.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mintlify/docs/guides/mail-platform.mdx b/mintlify/docs/guides/mail-platform.mdx index 021a87ff..4dd45aa5 100644 --- a/mintlify/docs/guides/mail-platform.mdx +++ b/mintlify/docs/guides/mail-platform.mdx @@ -478,9 +478,9 @@ Svix signatures are computed over raw payload bytes. `express.raw({ type: 'appli ### Secret configuration -Set `RESEND_WEBHOOK_SECRET` in server env. Without a valid secret, your deployment should fail closed for webhook processing. +Set `RESEND_WEBHOOK_SECRET` in server env. Webhook signature verification and event processing only occur when this secret is configured correctly. -- Fail-closed expectation: return HTTP `500` when secret is missing/misconfigured. +- 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` From 7b3f0ba384bbdf08e8aa2a0e61f00dde64701bd8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 12:09:36 +0000 Subject: [PATCH 5/6] docs: align mail platform guide with current mail implementation Agent-Logs-Url: https://github.com/geturbackend/urBackend/sessions/9780da1e-1d6c-4d54-8f41-a51ff2677c65 Co-authored-by: yash-pouranik <172860064+yash-pouranik@users.noreply.github.com> --- mintlify/docs/guides/mail-platform.mdx | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/mintlify/docs/guides/mail-platform.mdx b/mintlify/docs/guides/mail-platform.mdx index 4dd45aa5..512004d8 100644 --- a/mintlify/docs/guides/mail-platform.mdx +++ b/mintlify/docs/guides/mail-platform.mdx @@ -7,7 +7,8 @@ description: "Comprehensive developer guide for urBackend Mail Platform: BYOK, b urBackend Mail Platform extends transactional sending into a full delivery workflow with: -- async queue-backed sending (`/api/mail/send`, `/api/mail/send-batch`) +- 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) @@ -29,10 +30,12 @@ Implementation references: ```mermaid flowchart LR - A[Client request\n/api/mail/send or /api/mail/send-batch] --> B[Public API] + A1[Client request\n/api/mail/send] --> B[Public API] + A2[Client request\n/api/mail/send-batch] --> B B --> C[Quota reservation + BYOK/default provider resolve] - C --> D[BullMQ mail queue] + C --> D[BullMQ mail queue (single-send path)] D --> E[Resend API] + C --> E E --> F[Delivery events webhook] F --> G[POST /api/mail/webhook\nSvix verify] G --> H[(MailLog)] @@ -81,10 +84,10 @@ In Resend dashboard, configure webhook URL: Enable relevant events at minimum: +- `email.sent` - `email.delivered` - `email.bounced` - `email.complained` -- `email.failed` ## Sending emails @@ -215,7 +218,7 @@ console.log(result); ## Mail logs -### GET `/api/mail/logs` (public) and GET `/:projectId/mail/logs` (dashboard) +### GET `/api/mail/logs` (public) and GET `/api/projects/:projectId/mail/logs` (dashboard) #### `MailLog` fields @@ -486,10 +489,10 @@ Set `RESEND_WEBHOOK_SECRET` in server env. Webhook signature verification and ev | Resend event type | MailLog status | |---|---| +| `email.sent` | `sent` | | `email.delivered` | `delivered` | | `email.bounced` | `bounced` | | `email.complained` | `complained` | -| `email.failed` | `failed` | | `email.delivery_delayed` | `queued` | ### Retry behavior @@ -579,7 +582,7 @@ Add product screenshots/annotated walkthrough images in this section if your doc | `POST /api/mail/webhook` | `400` | Webhook signature verification failed | | Provider-proxied endpoints | passthrough | Upstream Resend status/message are surfaced | -All responses follow urBackend envelope shape: +Most endpoints follow urBackend envelope shape: ```json { @@ -589,6 +592,15 @@ All responses follow urBackend envelope shape: } ``` +Webhook signature failures currently return: + +```json +{ + "success": false, + "message": "Webhook signature verification failed." +} +``` + ## Changelog & migration notes Compared to legacy single-send usage: From 7f368998a6112a02a264d727783727642aa9e3f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 12:10:06 +0000 Subject: [PATCH 6/6] docs: clarify batch path and email.sent status semantics Agent-Logs-Url: https://github.com/geturbackend/urBackend/sessions/9780da1e-1d6c-4d54-8f41-a51ff2677c65 Co-authored-by: yash-pouranik <172860064+yash-pouranik@users.noreply.github.com> --- mintlify/docs/guides/mail-platform.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mintlify/docs/guides/mail-platform.mdx b/mintlify/docs/guides/mail-platform.mdx index 512004d8..2f292989 100644 --- a/mintlify/docs/guides/mail-platform.mdx +++ b/mintlify/docs/guides/mail-platform.mdx @@ -35,7 +35,7 @@ flowchart LR B --> C[Quota reservation + BYOK/default provider resolve] C --> D[BullMQ mail queue (single-send path)] D --> E[Resend API] - C --> E + C --> E[Resend API (batch-send direct path)] E --> F[Delivery events webhook] F --> G[POST /api/mail/webhook\nSvix verify] G --> H[(MailLog)] @@ -495,6 +495,8 @@ Set `RESEND_WEBHOOK_SECRET` in server env. Webhook signature verification and ev | `email.complained` | `complained` | | `email.delivery_delayed` | `queued` | +`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.