Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions mintlify/docs/api-reference/mail/send.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ Sends transactional email from your project.

### Request body

<ParamField body="to" type="string" required>
Recipient email address.
<ParamField body="to" type="string | string[]" required>
Recipient email address, or an array of recipient addresses. Each address must be a valid email. An array must contain at least one entry.
</ParamField>

<ParamField body="replyTo" type="string | string[]">
Optional reply-to address, or an array of addresses. Each address must be a valid email. When provided as an array, it must contain at least one entry. Forwarded to Resend as the message's `Reply-To` header.
</ParamField>

<ParamField body="subject" type="string">
Expand Down Expand Up @@ -57,7 +61,9 @@ Sends transactional email from your project.
- Template mode: `templateId` or `templateName`
- Direct mode: `subject` + (`html` or `text`)
- Do not pass both `templateId` and `templateName`.
- If rendered subject/body becomes empty after variable interpolation, request is rejected with `400`.
- `to` and `replyTo` must contain valid email addresses. Array forms cannot be empty (`Recipient list cannot be empty`, `Reply-to list cannot be empty`).
- Subject is required after any template resolution and variable interpolation (`Subject is required`).
- The rendered message must include a non-empty `html` or `text` body. Requests where both come out empty after interpolation are rejected with `Provide html or text` (or `Provide at least one of html or text content.` when neither field was supplied).

### Template resolution order

Expand Down Expand Up @@ -123,6 +129,7 @@ const res = await fetch('https://api.ub.bitbros.in/api/mail/send', {
},
body: JSON.stringify({
to: 'user@example.com',
replyTo: 'support@acme.com',
subject: 'Welcome to Acme',
html: '<h1>Hello</h1><p>Your account is ready.</p>'
})
Expand Down Expand Up @@ -212,10 +219,14 @@ public class Main {

### Errors

| Status | Cause |
| :--- | :--- |
| `400 Bad Request` | Invalid payload, invalid template ID, or empty rendered subject/body |
| `401 Unauthorized` | Missing project context |
| `403 Forbidden` | Non-secret key used |
| `404 Not Found` | Template or project not found |
| `429 Too Many Requests` | Monthly mail limit exceeded |
| Status | Message | Cause |
| :--- | :--- | :--- |
| `400 Bad Request` | `Invalid recipient email format` / `Recipient list cannot be empty` | `to` is missing, malformed, or an empty array |
| `400 Bad Request` | `Invalid replyTo email format` / `Reply-to list cannot be empty` | `replyTo` is malformed or an empty array |
| `400 Bad Request` | `Subject is required` | Subject missing, blank, or empty after template/variable interpolation |
| `400 Bad Request` | `Provide at least one of html or text content.` | Neither `html` nor `text` supplied (or both empty after interpolation) |
| `400 Bad Request` | `Template not found.` | `templateId` / `templateName` does not resolve |
| `401 Unauthorized` | `Project context missing.` | Request has no resolved project |
| `403 Forbidden` | `Forbidden. This action requires a Secret Key (sk_live_...).` | Non-secret key used |
| `404 Not Found` | `Project not found.` | Project record missing |
| `429 Too Many Requests` | `Monthly mail limit exceeded.` | Project quota exhausted for the month |
52 changes: 38 additions & 14 deletions mintlify/docs/guides/mail-platform.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +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 single sending (`/api/mail/send`)
- direct provider batch sending (`/api/mail/send-batch`)
- 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)
Expand Down Expand Up @@ -101,14 +101,21 @@ All `/api/mail/*` send endpoints require your **Secret Key** (`sk_live_...`) in

| Field | Type | Required | Notes |
|---|---|---:|---|
| `to` | `string` | ✅ | recipient email |
| `to` | `string \| string[]` | ✅ | recipient email(s); array form must have at least one entry |
| `replyTo` | `string \| string[]` | Optional | reply-to address(es); forwarded to Resend as the `Reply-To` header |
| `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 |

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
Expand Down Expand Up @@ -169,13 +176,30 @@ Array of 1..100 items:
[
{
"to": "u1@example.com",
"replyTo": "support@acme.com",
"subject": "Campaign",
"html": "<p>Hello</p>"
}
]
```

Each item supports `to`, `subject`, `html?`, `text?`.
Each item supports:

| Field | Type | Required | Notes |
|---|---|---:|---|
| `to` | `string \| string[]` | ✅ | recipient email(s); array form must have at least one entry |
| `replyTo` | `string \| string[]` | Optional | reply-to address(es); array form must have at least one entry |
| `subject` | `string` | ✅ | non-empty after trimming |
| `html` | `string` | one of `html/text` ✅ | HTML body |
| `text` | `string` | one of `html/text` ✅ | Text body |

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

Expand All @@ -196,8 +220,8 @@ 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>"}
{"to":"a@example.com","replyTo":"support@acme.com","subject":"Hi A","html":"<p>A</p>"},
{"to":"b@example.com","subject":"Hi B","text":"Hello B"}
]'
```

Expand All @@ -207,8 +231,8 @@ 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: '<p>A</p>' },
{ to: 'b@example.com', subject: 'Hi B', html: '<p>B</p>' }
{ to: 'a@example.com', replyTo: 'support@acme.com', subject: 'Hi A', html: '<p>A</p>' },
{ to: 'b@example.com', subject: 'Hi B', text: 'Hello B' }
]
});
console.log(result);
Expand Down Expand Up @@ -568,12 +592,12 @@ Add product screenshots/annotated walkthrough images in this section if your doc

| 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 |
| `POST /api/mail/send` | `400` | `Recipient list cannot be empty` / `Reply-to list cannot be empty` / `Subject is required` / `Provide at least one of html or text content.` / `Template not found.` |
| `POST /api/mail/send` | `403` | `Forbidden. This action requires a Secret Key (sk_live_...).` |
| `POST /api/mail/send` | `429` | `Monthly mail limit exceeded.` |
| `POST /api/mail/send-batch` | `400` | `Batch cannot be empty` / `Max 100 emails per batch` / `Recipient list cannot be empty` / `Reply-to list cannot be empty` / `Subject is required` / `Provide html or text` |
| `POST /api/mail/send-batch` | `403` | `Forbidden. This action requires a Secret Key (sk_live_...).` |
| `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 |
Expand Down
4 changes: 3 additions & 1 deletion mintlify/docs/sdk/mail.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ send(payload: SendMailPayload): Promise<SendMailResponse>

| Field | Type | Required | Description |
|---|---|---|---|
| `to` | `string \| string[]` | Yes | Recipient email address(es). |
| `to` | `string \| string[]` | Yes | Recipient email address(es). Array form must contain at least one entry. |
| `replyTo` | `string \| string[]` | No | Reply-to address(es). Forwarded to Resend as the `Reply-To` header. Array form must contain at least one entry. |
| `subject` | `string` | Template mode: No, Direct mode: Yes | Email subject line. |
| `text` | `string` | Direct mode: one of `text` or `html` is required | Plain text body. |
| `html` | `string` | Direct mode: one of `text` or `html` is required | HTML body. |
Expand Down Expand Up @@ -85,6 +86,7 @@ console.log(response.id);
// Direct mode
const response = await client.mail.send({
to: 'customer@example.com',
replyTo: 'support@acme.com',
subject: 'Welcome to urBackend',
html: '<h1>Hi there!</h1><p>Thanks for joining.</p>'
});
Expand Down
Loading