REST API Reference
Base URL
Section titled “Base URL”https://relaypost.dev/api/v1Authentication
Section titled “Authentication”All requests require an API key in the Authorization header:
Authorization: Bearer YOUR_API_KEYAPI keys are created in the RelayPost dashboard under Settings → API Keys. See API Keys for details.
Response format
Section titled “Response format”All successful responses are wrapped in a data envelope:
{ "data": { ... }}Paginated responses include a pagination object:
{ "data": [...], "pagination": { "page": 1, "limit": 20, "total_count": 150, "total_pages": 8 }}Error responses use a consistent error envelope:
{ "error": { "code": "VALIDATION_ERROR", "message": "Request validation failed", "details": [ { "field": "to", "message": "At least one recipient is required" } ] }}See Error Codes for the full list.
Rate limiting
Section titled “Rate limiting”Every response includes rate limit headers. See Rate Limits for details.
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the current window |
Retry-After | Seconds to wait before retrying (only on 429 responses) |
Emails
Section titled “Emails”Send an email
Section titled “Send an email”POST /api/v1/emails/send
curl -X POST https://relaypost.dev/api/v1/emails/send \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "from": { "email": "[email protected]", "name": "Your App" }, "to": [{ "email": "[email protected]" }], "subject": "Your receipt", "html": "<h1>Thanks for your purchase!</h1>" }'const response = await fetch("https://relaypost.dev/api/v1/emails/send", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer YOUR_API_KEY", }, body: JSON.stringify({ subject: "Your receipt", html: "<h1>Thanks for your purchase!</h1>", }),});
const result = await response.json();console.log(result.data.message_id);import requests
response = requests.post( "https://relaypost.dev/api/v1/emails/send", headers={ "Content-Type": "application/json", "Authorization": "Bearer YOUR_API_KEY", }, json={ "subject": "Your receipt", "html": "<h1>Thanks for your purchase!</h1>", },)
result = response.json()print(result["data"]["message_id"])Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
from | object | Yes | Sender — { "email": "...", "name": "..." } |
to | array | Yes | Recipients — [{ "email": "...", "name": "..." }] |
cc | array | No | CC recipients |
bcc | array | No | BCC recipients |
subject | string | Yes | Email subject line |
html | string | No | HTML body |
text | string | No | Plain text body |
template_id | string | No | Use a saved template instead of inline content |
template_data | object | No | Key-value pairs for template variables |
headers | object | No | Custom email headers |
priority | string | No | high, normal (default), or low |
scheduled_at | string | No | ISO 8601 datetime for scheduled delivery |
You must provide either html, text, or template_id.
Response (201)
Section titled “Response (201)”{ "data": { "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "queued", "queued_at": "2025-01-15T10:30:00.000Z" }}Errors
Section titled “Errors”| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing or invalid fields |
| 400 | DOMAIN_NOT_VERIFIED | From address uses an unverified domain |
| 400 | RECIPIENTS_SUPPRESSED | One or more recipients are on the suppression list |
| 429 | LIMIT_EXCEEDED | Organization email sending limit exceeded |
List emails
Section titled “List emails”GET /api/v1/emails
Query parameters
Section titled “Query parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | — | Filter by status: queued, delivered, bounced, failed, opened |
from_date | string | — | ISO 8601 start date |
to_date | string | — | ISO 8601 end date |
page | integer | 1 | Page number |
limit | integer | 20 | Results per page (max 100) |
Response (200)
Section titled “Response (200)”{ "data": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "subject": "Your receipt", "status": "delivered", "priority": "normal", "created_at": "2025-01-15T10:30:00.000Z", "updated_at": "2025-01-15T10:30:05.000Z" } ], "pagination": { "page": 1, "limit": 20, "total_count": 42, "total_pages": 3 }}Get email by ID
Section titled “Get email by ID”GET /api/v1/emails/:id
Retrieve a specific email by its ID or message ID, including delivery events.
Response (200)
Section titled “Response (200)”{ "data": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "subject": "Your receipt", "status": "delivered", "priority": "normal", "created_at": "2025-01-15T10:30:00.000Z", "updated_at": "2025-01-15T10:30:05.000Z", "scheduled_at": null, "events": [ { "id": "evt_001", "type": "delivered", "smtp_code": 250, "smtp_message": "OK", "created_at": "2025-01-15T10:30:05.000Z" } ] }}Errors
Section titled “Errors”| Status | Code | Description |
|---|---|---|
| 404 | NOT_FOUND | Email not found or belongs to another organization |
Domains
Section titled “Domains”Add a domain
Section titled “Add a domain”POST /api/v1/domains
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
domain | string | Yes | The domain name (e.g. example.com) |
Response (201)
Section titled “Response (201)”{ "data": { "id": "dom_abc123", "domain": "example.com", "is_verified": false, "spf_verified": false, "dkim_verified": false, "dkim_selector": "relaypost", "dkim_public_key": "MIGfMA0GCSqGSIb3DQEBAQUAA4...", "dns_records": [ { "type": "TXT", "name": "relaypost._domainkey.example.com", "value": "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4..." }, { "type": "TXT", "name": "example.com", "value": "v=spf1 include:relaypost.dev ~all" } ], "created_at": "2025-01-15T10:30:00.000Z" }}List domains
Section titled “List domains”GET /api/v1/domains
Response (200)
Section titled “Response (200)”{ "data": [ { "id": "dom_abc123", "domain": "example.com", "is_verified": true, "spf_verified": true, "dkim_verified": true, "dkim_selector": "relaypost", "dkim_public_key": "MIGfMA0GCSqGSIb3DQEBAQUAA4...", "verified_at": "2025-01-15T11:00:00.000Z", "created_at": "2025-01-15T10:30:00.000Z" } ]}Verify domain DNS
Section titled “Verify domain DNS”GET /api/v1/domains/:id/verify
Performs DNS verification and returns the updated domain status.
Response (200)
Section titled “Response (200)”{ "data": { "id": "dom_abc123", "domain": "example.com", "is_verified": true, "spf_verified": true, "dkim_verified": true, "dkim_selector": "relaypost", "dkim_public_key": "MIGfMA0GCSqGSIb3DQEBAQUAA4...", "verified_at": "2025-01-15T11:00:00.000Z", "created_at": "2025-01-15T10:30:00.000Z" }}Errors
Section titled “Errors”| Status | Code | Description |
|---|---|---|
| 404 | NOT_FOUND | Domain not found or belongs to another organization |
Suppressions
Section titled “Suppressions”List suppressions
Section titled “List suppressions”GET /api/v1/suppressions
Query parameters
Section titled “Query parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 20 | Results per page (max 100) |
Response (200)
Section titled “Response (200)”{ "data": [ { "id": "sup_abc123", "reason": "hard_bounce", "source": "automatic", "created_at": "2025-01-15T10:30:00.000Z" } ], "pagination": { "page": 1, "limit": 20, "total_count": 15, "total_pages": 1 }}Add a suppression
Section titled “Add a suppression”POST /api/v1/suppressions
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address to suppress |
reason | string | Yes | One of: hard_bounce, soft_bounce, complaint, unsubscribe, manual |
Response (201)
Section titled “Response (201)”{ "data": { "id": "sup_def456", "reason": "hard_bounce", "source": "manual", "created_at": "2025-01-15T10:30:00.000Z" }}Errors
Section titled “Errors”| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid email or reason |
Remove a suppression
Section titled “Remove a suppression”DELETE /api/v1/suppressions/:id
Response (200)
Section titled “Response (200)”{ "data": { "id": "sup_abc123", "deleted": true }}Errors
Section titled “Errors”| Status | Code | Description |
|---|---|---|
| 404 | NOT_FOUND | Suppression not found or belongs to another organization |
Templates
Section titled “Templates”List templates
Section titled “List templates”GET /api/v1/templates
Query parameters
Section titled “Query parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 20 | Results per page (max 100) |
Response (200)
Section titled “Response (200)”{ "data": [ { "id": "tmpl_abc123", "name": "Order Confirmation", "subject": "Your order #{{orderNumber}}", "html_body": "...", "text_body": "...", "variables": null, "is_active": true, "created_at": "2025-01-15T10:30:00.000Z", "updated_at": "2025-01-15T10:30:00.000Z" } ], "pagination": { "page": 1, "limit": 20, "total_count": 5, "total_pages": 1 }}Create a template
Section titled “Create a template”POST /api/v1/templates
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Template name |
subject | string | Yes | Subject line (supports {{variables}}) |
html | string | No | HTML body (supports {{variables}}) |
text | string | No | Plain text body (supports {{variables}}) |
Response (201)
Section titled “Response (201)”{ "data": { "id": "tmpl_abc123", "name": "Order Confirmation", "subject": "Your order #{{orderNumber}}", "html_body": "<h1>Thanks, {{customerName}}!</h1>", "text_body": "Thanks, {{customerName}}!", "variables": null, "is_active": true, "created_at": "2025-01-15T10:30:00.000Z", "updated_at": "2025-01-15T10:30:00.000Z" }}Update a template
Section titled “Update a template”PUT /api/v1/templates/:id
Only include the fields you want to change.
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Updated template name |
subject | string | No | Updated subject line |
html | string | No | Updated HTML body |
text | string | No | Updated plain text body |
At least one field must be provided.
Response (200)
Section titled “Response (200)”{ "data": { "id": "tmpl_abc123", "name": "Order Confirmation", "subject": "Your order #{{orderNumber}}", "html_body": "<h1>Updated layout</h1>", "text_body": "Thanks, {{customerName}}!", "variables": null, "is_active": true, "created_at": "2025-01-15T10:30:00.000Z", "updated_at": "2025-01-15T11:00:00.000Z" }}Errors
Section titled “Errors”| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | No update fields provided or invalid values |
| 404 | NOT_FOUND | Template not found or belongs to another organization |
Delete a template
Section titled “Delete a template”DELETE /api/v1/templates/:id
Response (200)
Section titled “Response (200)”{ "data": { "id": "tmpl_abc123", "deleted": true }}Errors
Section titled “Errors”| Status | Code | Description |
|---|---|---|
| 404 | NOT_FOUND | Template not found or belongs to another organization |