RELYPOST · DEVELOPER GUIDE

API documentation

Set up a sender, send your first email, and track what happens next. Copy the examples into your server application.

REST API · v1https://api.relypost.com/v1No sign-in required to read

Transactional email API for your application

RelyPost is a transactional email REST API for applications sending order confirmations, password resets, receipts and other service notifications from their own verified domains. API v1 supports plain text and HTML, inline images, file attachments, scheduled sending, message status, domain inspection and signed webhooks. Use it from any language that can make HTTPS requests.

Start with the quickstart, then use the full endpoint reference below. Download the Markdown guide for offline reading or import the OpenAPI specification into compatible API tools. See plans and pricing and your workspace Billing settings for allowances.

GET STARTED

1. Set up your account

Everyone can read this guide, including organization members. An organization owner or administrator creates API keys and manages sending domains. If you are a member, ask your administrator to set up the integration and store its key on your application server.

  1. Enable Email API service. Ask RelyPost support or your platform administrator to activate API access for your organization. Review the Email API plan in Developer → Billing. Creating a key alone does not activate the API.
  2. Connect a sending domain. Open Developer → Domains and choose an existing verified, active domain, or add a domain and complete its DNS setup and verification.
  3. Create an Email API sender. In Developer → Mailboxes, add an API sender such as hello@your-domain.com. Creating the sender enables the domain for Email API within your plan limits. Existing domains do not need to be added or verified again. Wait until the sender is active. A regular employee or shared mailbox cannot be used as an API sender.
  4. Create a scoped key. Open Developer → API keys and select emails:send and emails:read for this quickstart. Copy the key when it appears; it is shown only once.
Keep credentials on your server. API keys and SMTP app passwords are different credentials. Do not put an API key in frontend JavaScript, a mobile app, a public repository, or a URL.
Open Developer settings

2. Send your first email

Replace the sender with your active API sender and the recipient with your test address. Set RELYPOST_API_KEY in your server environment or secret manager. These examples read that variable; do not paste a real key into this page.

For each new application event, choose a stable, unique Idempotency-Key, such as an order confirmation ID. If a request times out, retry its exact body with the same key.

cURL send example
curl --fail-with-body 'https://api.relypost.com/v1/emails' \
  -H "Authorization: Bearer $RELYPOST_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: order-123-confirmation' \
  --data '{
  "from": "hello@your-domain.com",
  "to": [
    "customer@example.com"
  ],
  "subject": "Your order confirmation",
  "text": "Thank you for your order."
}'

Successful response · HTTP 202

Queued response example
{
  "id": "a19f458e-a915-4aac-8ea1-8c859b41d934",
  "state": "queued",
  "revision": 1,
  "scheduled_at": "2026-09-14T12:00:00.000Z"
}

The UUID in id identifies this message. Save it in your application. 202 and queued mean the request was saved for sending; they do not mean the recipient received the email. Dates and IDs shown here are illustrative.

Send HTML email and inline images

Set html to your rendered HTML string and text to a useful plain-text alternative. RelyPost passes these bodies to the email transport. There is no template rendering endpoint or automatic CSS inlining service. Use email-compatible HTML, inline styles and meaningful image alt text; escape untrusted values before inserting them into a template.

For a hosted image, use an absolute HTTPS URL. For an attached image, upload and finalize the image first, then set its cid and reference exactly that value with cid: in the HTML. The example file ID below is a placeholder; replace it with your ready image ID. Test the rendered email in the mail clients your recipients use.

HTML email request body
{
  "from": "hello@your-domain.com",
  "to": [
    "customer@example.com"
  ],
  "subject": "Your order confirmation",
  "text": "Thank you for your order.",
  "html": "<!doctype html><html lang=\"en\"><body><h1>Thank you for your order</h1><p>Your order <strong>#123</strong> is confirmed.</p><p><a href=\"https://your-app.com/orders/123\">View your order</a></p></body></html>"
}
Inline image request body
{
  "from": "hello@your-domain.com",
  "to": [
    "customer@example.com"
  ],
  "subject": "Your order confirmation",
  "text": "Thank you for your order.",
  "html": "<h1>Order confirmed</h1><img src=\"cid:company-logo\" width=\"160\" alt=\"Company logo\"><p>Thank you for your order.</p>",
  "attachments": [
    {
      "id": "a19f458e-a915-4aac-8ea1-8c859b41d934",
      "cid": "company-logo"
    }
  ]
}

Schedule email and preserve threading

Add scheduled_at as an ISO 8601 UTC timestamp ending in Z, no more than 365 days in the future. Omit it to queue immediately. A past timestamp is eligible for immediate processing. A schedule is the earliest sending time; queues, quotas and delivery retries can delay it. There is no public endpoint to cancel or modify an accepted email.

Generate a future timestamp once and persist the entire request with its Idempotency-Key. Do not recompute scheduled_at on a retry: a changed payload conflicts with the original key. Uploads have a staging lifetime, so prepare attachments close to the sending time rather than assuming they remain available for a distant schedule.

For a reply, set in_reply_to to the original RFC Message-ID and references to the prior Message-ID chain. These values use angle brackets, such as <original@your-domain.com>; they are different from RelyPost’s message UUID. Set reply_to to an email address for responses. priority is high, normal or low; it is not a delivery-speed guarantee. tags, metadata and X- headers let you associate messages with application context; they do not create search or filtering endpoints.

Node.js scheduled email example
// Node.js 18+. Generate once; persist payload and key before the first attempt.
const payload = {
  ...{
  "from": "hello@your-domain.com",
  "to": [
    "customer@example.com"
  ],
  "subject": "Your order confirmation",
  "text": "Thank you for your order."
},
  scheduled_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
};
const idempotencyKey = 'order-123-followup';
// Reuse these exact values for every retry of this application event.
const response = await fetch('https://api.relypost.com/v1/emails', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.RELYPOST_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': idempotencyKey,
  },
  body: JSON.stringify(payload),
});
const result = await response.json();
if (!response.ok) throw new Error(JSON.stringify(result.error));
console.log(result.id, result.scheduled_at);

Response conventions and integration limits

The public base URL ends in /v1. All 12 operations require a Bearer API key and return JSON. Paths below are relative to that base; UUID path values identify resources inside the key’s organization. Send application/json for request bodies. No list operation accepts a documented pagination, search or filter parameter; webhook delivery history returns at most 100 records.

Unknown top-level email fields are rejected. Omitted optional fields receive the defaults shown in OpenAPI; null is not an omission. API email addresses are lowercased by validation. Responses can contain null for timestamps or diagnostics that are not yet available. Treat new response fields and unknown event types as forward-compatible additions.

Success responses include X-Request-Id. Errors use error.code, error.message and error.requestId; validation errors may also include error.fields entries with path and message. Keep the request ID for support. Never log your API key, SMTP app password, webhook secret or signed upload URL.

Request JSON is limited to 1,500,000 bytes. Default combined recipients: 20; each address array has a schema maximum of 100. Default complete MIME message limit: 25 MiB, including headers and attachment encoding. Default upload limit: 100 MiB, which does not mean a 100 MiB file fits in an email. Other limits depend on the deployment and plan. Upload sessions last one hour; API attachment staging defaults to 24 hours.

Sending quotas count recipients at admission, including cc and bcc. Dispatch rate limits can keep accepted messages queued. There is no public endpoint for querying remaining quota or a promise of rate-limit headers. See Developer → Billing. Message status is available only within your plan’s log-retention window.

For timeouts or temporary failures, retry the identical payload with the same organization-scoped Idempotency-Key and exponential backoff with jitter. Reusing a key with changed content returns 409 idempotency_conflict. Use a new key for each new application event. Do not treat idempotency as a permanent archive after data retention; store your own business-event/message mapping.

3. Track delivery

Use the returned message UUID with a key that has emails:read. Replace the example UUID below with your actual message ID.

Read message status
curl --fail-with-body 'https://api.relypost.com/v1/emails/a19f458e-a915-4aac-8ea1-8c859b41d934' \
  -H "Authorization: Bearer $RELYPOST_API_KEY"

Read state, recipients, recipient_retry, and events. Recipient entries include SMTP status, the remote response and observation time. Use webhooks for ongoing updates instead of polling continuously. Administrators can also open Developer → Delivery logs.

StateMeaning
queuedSaved for sending or waiting for schedule, quota or sending capacity.
submittingAn SMTP attempt is in progress.
submittedRelyPost’s submission server accepted responsibility. Check recipient states for destination acceptance.
accepted (recipient)The destination server accepted this recipient. This does not establish inbox placement or reading.
deferredA temporary failure or delay. Eligible temporary failures are retried with a bounded budget.
uncertainThe server outcome is unknown. RelyPost reconciles evidence; do not create a new send or change the idempotency key to force a resend.
rejected / bouncedA permanent failure, exhausted retry budget, or recipient bounce. Read the diagnostic and per-recipient response.
cancelledThe queued operation was cancelled. There is no public API cancellation endpoint.

Authentication & scopes

All endpoints below use the base URL https://api.relypost.com/v1 and an Authorization: Bearer rp_live_… header. JSON request bodies require Content-Type: application/json. UUID placeholders in paths must be replaced with real IDs.

ScopePermission
emails:sendSend application email.
emails:readRead message status, recipients and events.
domains:readRead your organization’s domains and DNS details.
attachments:writeReserve, upload and finalize attachments.
webhooks:manageRegister endpoints and manage webhook deliveries.

Grant only the scopes your app uses. Keys belong to one organization; resources from another organization are not accessible. The key creator must remain a verified owner or administrator. If a key is exposed or its creator leaves, replace it and revoke the old key in Developer → API keys.

This is a server-to-server API. A workspace session cookie, login password or SMTP password is not a Bearer API key. Keep user-facing apps behind your own backend.

Endpoint reference

Paths are relative to https://api.relypost.com/v1. JSON examples use placeholder data. Expand an endpoint to see its request and response.

12 endpoints

POST/emailsemails:send
Link to POST /emails

Queue one message. Requires Idempotency-Key (8–200 characters). Returns 202; save the id for tracking.

POST /emails request
{
  "from": "hello@your-domain.com",
  "to": [
    "customer@example.com"
  ],
  "subject": "Your order confirmation",
  "text": "Thank you for your order."
}
POST /emails response
{
  "id": "a19f458e-a915-4aac-8ea1-8c859b41d934",
  "state": "queued",
  "revision": 1,
  "scheduled_at": "2026-09-14T12:00:00.000Z"
}
GET/emails/{id}emails:read
Link to GET /emails/{id}

Read one API message from this organization within your plan’s log-retention window. Use the UUID returned by POST, not the RFC Message-ID.

GET /emails/{id} response
{
  "id": "a19f458e-a915-4aac-8ea1-8c859b41d934",
  "state": "submitted",
  "message_id": "<message@mail01.relypost.com>",
  "created_at": "2026-09-14T12:00:00.000Z",
  "scheduled_at": "2026-09-14T12:00:00.000Z",
  "submitted_at": "2026-09-14T12:00:01.000Z",
  "smtp_code": "250",
  "diagnostic": "Accepted by submission server",
  "recipients": [
    {
      "address": "customer@example.com",
      "kind": "to",
      "state": "accepted",
      "enhanced_status": "2.0.0",
      "remote_response": "status=sent (250 accepted)",
      "observed_at": "2026-09-14T12:00:02.000Z"
    }
  ],
  "recipient_retry": null,
  "events": [
    {
      "type": "email.queued",
      "created_at": "2026-09-14T12:00:00.000Z",
      "details": {}
    }
  ]
}
GET/domainsdomains:read
Link to GET /domains

List your organization’s domains. This API reads domains; add or verify them in Developer → Domains.

GET /domains response
[
  {
    "id": "a19f458e-a915-4aac-8ea1-8c859b41d934",
    "name": "your-domain.com",
    "status": "active",
    "verified_at": "2026-09-14T10:00:00.000Z"
  }
]
GET/domains/{id}domains:read
Link to GET /domains/{id}

Read a domain’s status and DNS configuration. Returns id, name, status, verified_at, dkim_selector, dkim_txt and dns_state.

GET /domains/{id} response
{
  "id": "a19f458e-a915-4aac-8ea1-8c859b41d934",
  "name": "your-domain.com",
  "status": "active",
  "verified_at": "2026-09-14T10:00:00.000Z",
  "dkim_selector": "dkim",
  "dkim_txt": "v=DKIM1; k=rsa; p=YOUR_PUBLIC_KEY",
  "dns_state": []
}
POST/attachmentsattachments:write
Link to POST /attachments

Reserve an upload; returns 201. id is the eventual attachment ID; sessionId is used for uploading/finalizing. For multipart:false, the response also includes url and fields.

POST /attachments request
{
  "name": "receipt.pdf",
  "size": 12345,
  "type": "application/pdf",
  "multipart": false
}
POST /attachments response
{
  "id": "a19f458e-a915-4aac-8ea1-8c859b41d934",
  "sessionId": "94f3e2c8-ff6e-45cf-90ef-8b037111ae64",
  "multipart": false,
  "url": "https://your-signed-storage-url.example",
  "fields": {
    "key": "temporary/…",
    "policy": "…",
    "x-amz-signature": "…"
  }
}
POST/attachments/{sessionId}/partattachments:write
Link to POST /attachments/{sessionId}/part

For multipart uploads only: get a signed PUT URL for a 1-based part number. Upload exactly that part’s bytes to the URL and save its ETag.

POST /attachments/{sessionId}/part request
{
  "part": 1
}
POST /attachments/{sessionId}/part response
{
  "url": "https://your-signed-storage-url.example/part"
}
POST/attachments/{sessionId}/finalizeattachments:write
Link to POST /attachments/{sessionId}/finalize

Verify and scan the upload. Single uploads use parts:[]; multipart uploads use ordered {PartNumber,ETag} entries. Only state:ready can be attached to email.

POST /attachments/{sessionId}/finalize request
{
  "parts": []
}
POST /attachments/{sessionId}/finalize response
{
  "id": "a19f458e-a915-4aac-8ea1-8c859b41d934",
  "state": "ready"
}
GET/webhookswebhooks:manage
Link to GET /webhooks

List endpoints. Returns id, url, enabled, failures and created_at. Secrets are not returned.

GET /webhooks response
[
  {
    "id": "a19f458e-a915-4aac-8ea1-8c859b41d934",
    "url": "https://your-app.com/webhooks/relypost",
    "enabled": true,
    "failures": 0,
    "created_at": "2026-09-14T12:00:00.000Z"
  }
]
POST/webhookswebhooks:manage
Link to POST /webhooks

Register a public HTTPS endpoint on port 443, without URL credentials or a fragment. Returns 201 and a secret shown only once. No event-selection field is supported.

POST /webhooks request
{
  "url": "https://your-app.com/webhooks/relypost"
}
POST /webhooks response
{
  "id": "a19f458e-a915-4aac-8ea1-8c859b41d934",
  "secret": "SAVE_THE_ONE_TIME_SECRET"
}
PATCH/webhooks/{id}webhooks:manage
Link to PATCH /webhooks/{id}

Enable or disable an existing endpoint. This also resets its failure counter.

PATCH /webhooks/{id} request
{
  "enabled": true
}
PATCH /webhooks/{id} response
{
  "ok": true
}
GET/webhooks/{id}/deliverieswebhooks:manage
Link to GET /webhooks/{id}/deliveries

Read up to 100 webhook delivery records. Fields: id, event_id, state, attempts, last_status and next_attempt_at. This endpoint has no pagination parameters.

GET /webhooks/{id}/deliveries response
[
  {
    "id": "94f3e2c8-ff6e-45cf-90ef-8b037111ae64",
    "event_id": "a19f458e-a915-4aac-8ea1-8c859b41d934",
    "state": "pending",
    "attempts": 0,
    "last_status": null,
    "next_attempt_at": "2026-09-14T12:00:00.000Z"
  }
]
POST/webhooks/{id}/retrywebhooks:manage
Link to POST /webhooks/{id}/retry

Requeue an existing delivery using its delivery-record id. Expired delivery payloads cannot be retried. This retries the webhook notification, not the email.

POST /webhooks/{id}/retry request
{
  "deliveryId": "94f3e2c8-ff6e-45cf-90ef-8b037111ae64"
}
POST /webhooks/{id}/retry response
{
  "ok": true
}

The public API does not provide mailbox reading, message listing, domain creation, email cancellation, or marketing campaigns. Manage mailboxes and domains in the workspace. Do not integrate against the workspace’s internal /api/org/… session routes.

Email request fields

POST /emails accepts the fields below. Unknown fields are rejected. Email addresses are plain strings, not name/address objects.

FieldTypeHow to use it
fromstring · requiredFull address of an active Email API sender on your verified, API-enabled domain. Display names such as “Team <hello@…>” are not accepted.
tostring[] · requiredAt least one recipient. Do not repeat an address across to, cc and bcc. The combined recipient limit is 20 by default; your deployment may configure a different limit.
subjectstring · requiredUp to 998 characters, with no line breaks.
text / htmlstringPlain text and/or HTML body, up to 500,000 characters each. Provide at least one for a useful message. text defaults to an empty string.
cc / bccstring[]Additional recipients; each array accepts up to 100 addresses, subject to the combined recipient limit.
reply_tostringOne reply-to email address.
attachments{id, cid?}[]Up to 20 finalized file IDs. An optional cid (1–100 letters, digits, _, ., @ or -) lets HTML reference an inline attachment with cid:your-id.
scheduled_atstringOptional ISO 8601 UTC timestamp ending in Z. Schedule no more than 365 days ahead. Omit for immediate queuing.
headersobjectCustom X- headers only. Names: X- plus 1–60 letters, digits or hyphens; values: up to 500 characters with no line breaks.
tagsstring[]Up to 10 tags, each up to 50 characters.
metadataobjectUp to 20 string values; keys up to 50 characters and values up to 500.
in_reply_to / referencesstring / string[]Message-ID values such as <message@example.com>. Up to 30 references, each up to 998 characters.
prioritystringhigh, normal (default), or low.

Send attachments

Use a key with attachments:write to upload and emails:send to send. Uploading alone does not make a file ready: finalize it successfully before referencing its ID.

  1. Reserve a file with POST /attachments, providing its name, byte size, MIME type, and multipart:false.
  2. Send every returned field, followed by the file, as multipart form data to the returned signed storage URL. Do not send your API key to storage.
  3. Call POST /attachments/{sessionId}/finalize with {"parts":[]}. Scanning and validation must return state:ready.
  4. Add "attachments":[{"id":"the-finalized-file-UUID"}] to a new email request.
Node.js attachment upload example
// Node.js 20+; upload a file, finalize it, then attach its ID.
import { readFile } from 'node:fs/promises';
const file = await readFile('./receipt.pdf');
async function api(path, body) {
  const response = await fetch('https://api.relypost.com/v1' + path, {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.RELYPOST_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  const result = await response.json();
  if (!response.ok) throw new Error(JSON.stringify(result));
  return result;
}
const upload = await api('/attachments', {
  name: 'receipt.pdf', size: file.length, type: 'application/pdf', multipart: false,
});
const form = new FormData();
for (const [key, value] of Object.entries(upload.fields)) form.append(key, String(value));
form.append('file', new Blob([file], { type: 'application/pdf' }), 'receipt.pdf');
// Do not send your API key or set Content-Type on this storage request.
const stored = await fetch(upload.url, { method: 'POST', body: form });
if (!stored.ok) throw new Error('File upload failed: ' + stored.status);
const ready = await api('/attachments/' + upload.sessionId + '/finalize', { parts: [] });
if (ready.state !== 'ready') throw new Error('Attachment is not ready');
console.log(ready.id);
// Add attachments: [{ id: ready.id }] to your POST /emails payload.
// Use a new Idempotency-Key for this new message.

Large files and multipart uploads

Reserve with multipart:true. The response gives id, sessionId, partSize (5,242,880 bytes) and parts. Request a signed URL for each 1-based part with {"part":1}, PUT that part’s bytes, and retain its exact ETag. Finalize with all PartNumber/ETag pairs in ascending order. The last part may be smaller.

Upload sessions expire after one hour. API attachment staging defaults to 24 hours; create attachments close to the send time, especially for scheduled messages. The key creator must still be an organization member. Storage allowance, scanning, and message-size limits apply.

Receive delivery webhooks

Register a public HTTPS URL with POST /webhooks and the webhooks:manage scope. Store the returned secret immediately; listing endpoints will not reveal it again. Endpoints receive organization events; inspect type to decide what to process.

Register a webhook
curl --fail-with-body 'https://api.relypost.com/v1/webhooks' \
  -H "Authorization: Bearer $RELYPOST_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"url":"https://your-app.com/webhooks/relypost"}'
Webhook payload example
{
  "id": "94f3e2c8-ff6e-45cf-90ef-8b037111ae64",
  "type": "email.recipient_accepted",
  "created_at": "2026-09-14T12:00:02.000Z",
  "data": {
    "id": "a19f458e-a915-4aac-8ea1-8c859b41d934",
    "recipient": "customer@example.com",
    "dsn": "2.0.0",
    "response": "status=sent (250 accepted)",
    "occurred_at": "2026-09-14T12:00:02.000Z"
  }
}

data.id is the email UUID; the top-level id is the event UUID. Common types include email.queued, email.submitted, email.recipient_accepted, email.recipient_deferred, and email.bounced. Handle unknown event types without failing the entire endpoint.

Event typeMeaning
email.queuedThe email was saved for sending, including scheduled messages.
email.submittedThe submission server accepted responsibility for the email.
email.deferredThe message was deferred after a temporary submission problem.
email.uncertainSubmission outcome is unknown; wait for reconciliation instead of creating another send.
email.rejectedSubmission failed permanently or exhausted its retry budget.
email.recipient_acceptedThe destination server accepted a recipient; this is not a read receipt.
email.recipient_deferredA recipient had a temporary delivery failure.
email.recipient_resubmittedA retry for a deferred recipient was submitted.
email.bouncedA recipient could not be delivered to, or a bounce was reported.
email.submission_recoveredReconciliation recovered evidence about a previously uncertain submission.
email.complainedA complaint was reported by a configured feedback provider; availability depends on that provider.

Event data varies by type; recipient and SMTP response fields are not present on every event. Additional organization events may also arrive. Inspect the type before processing the data.

Verify the signature before parsing JSON

Read the headers webhook-id, webhook-timestamp (Unix seconds), and webhook-signature (hex). The signature is HMAC-SHA256 over id.timestamp.rawBody, using your webhook secret. Reject timestamps more than five minutes in the past or future, compare signatures in constant time, and deduplicate webhook-id durably.

Node.js webhook signature verification
import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody must be the exact request bytes, before JSON parsing.
// headers is a Headers object; secret is your saved webhook secret.
export function verifyRelyPostWebhook(rawBody, headers, secret, now = Date.now()) {
  const id = headers.get('webhook-id');
  const timestamp = headers.get('webhook-timestamp');
  const signature = headers.get('webhook-signature') ?? '';
  if (!id || !timestamp || !/^\d+$/.test(timestamp)) return false;
  if (!/^[a-f0-9]{64}$/i.test(signature)) return false;
  if (Math.abs(now / 1000 - Number(timestamp)) > 300) return false;
  const expected = createHmac('sha256', secret)
    .update(id + '.' + timestamp + '.')
    .update(rawBody)
    .digest();
  return timingSafeEqual(expected, Buffer.from(signature, 'hex'));
}

// In your webhook handler:
// 1. Read the raw body and verify it; return 401 on failure.
// 2. Parse JSON only after verification.
// 3. Atomically store/enqueue the event using webhook-id as a UNIQUE key.
// 4. Return 2xx after durable acceptance, including for a duplicate.
// 5. Process stored events in your worker; do not send another email here.

Retries may repeat notifications and delivery order is not guaranteed. Return a 2xx response after durable acceptance, including for a duplicate. Respond promptly and process work asynchronously. Failed requests retry with bounded backoff; repeatedly failing endpoints are disabled. Inspect deliveries, fix the handler, enable the endpoint and retry the failed delivery ID. Redirects are not followed.

Retries, scheduling & limits

  • Retry the same send safely. Keep the original payload and Idempotency-Key for timeouts or transient server errors. The same key with different input returns 409 idempotency_conflict. A new message requires a new key.
  • Respect uncertain outcomes. Read the message status and wait for reconciliation. Creating a fresh send can duplicate a message the SMTP server already accepted.
  • Plan for queues. Rate controls, sending capacity and scheduled timestamps can delay an admitted message. Monthly plan usage counts recipients; creating more keys does not bypass organization limits.
  • Keep requests bounded. Defaults are 20 combined recipients and 25 MiB per final MIME message, including attachment encoding and overhead. The JSON body limit is 1,500,000 bytes. Individual file uploads default to 100 MiB, but files in an email must fit its smaller MIME limit. Deployment settings may lower or raise configurable limits.
  • Retain your own message IDs. Read access to status and events expires with your API plan’s log-retention window. Store events your application needs to retain longer.

Troubleshooting

Check the HTTP status and error.code. Validation errors may include error.fields. Include error.requestId when contacting support; successful responses also include an X-Request-Id header.

Error response example
{
  "error": {
    "code": "missing_scope",
    "message": "API key requires emails:send",
    "requestId": "a19f458e-a915-4aac-8ea1-8c859b41d934"
  }
}
HTTPCode / causeWhat to do
400duplicate_recipient / recipient_limit / metadata_limit / schedule_too_farUse unique recipients within the combined limit, no more than 20 metadata keys, and a schedule within 365 days.
400invalid_folder / invalid_part / invalid_partsUse a folder without . or .. segments, a part number within the reserved file, and a complete ordered PartNumber/ETag list.
400invalid_webhook / unsafe_webhookUse a public HTTPS receiver on port 443. Private IPs, URL credentials and fragments are rejected.
403key_owner_unavailable / organization_suspendedAsk an active administrator to review organization access and rotate the key if its owner is unavailable.
413size_mismatchUpload nonempty bytes within the reserved size, then finalize. Start a new reservation if the file changed.
422unsafe_fileThe file did not pass malware scanning. Do not retry or attach the same unsafe file.
502 / 503storage_copy_failed / versioning_required / disk_emergencyAttachment storage is unavailable or needs operator attention. Contact support with the request ID; only attach files after finalization returns ready.
400validation_error / invalid_json / idempotency_requiredCheck error.fields, send valid JSON and supply an Idempotency-Key of 8–200 characters.
401invalid_api_keyCheck the Bearer key, revocation, organization API activation, and whether its creator is still a verified owner/admin. A paused organization can also make a key invalid.
402billing_attentionAsk an administrator to restore the service in Billing.
403missing_scope / sender_not_authorized / api_sender_required / plan_limitUse the required scope and an active Email API sender on a verified API-enabled domain within plan limits.
404not_found / attachment_not_found / delivery_expiredUse the correct organization key and resource ID. Finalize attachments first. Check log retention.
409idempotency_conflict / plan_limit / quota_exceeded / upload_expiredReuse a key only with the same payload. Review Billing or start a new upload session if the previous one expired.
413 / 415message_too_large / file_too_large / request_too_large / json_requiredReduce the body or attachment size; API request bodies must use application/json. Upload file bytes only to signed storage URLs.
422recipient_suppressedReview Suppressions with your administrator. A blocked recipient prevents this request from queuing.
429Rate limitBack off with jitter and retry the same payload with the same idempotency key. Sending limits may also leave admitted messages queued.
500 / 502 / 503internal_error / upstream error / api_disabled / recovery_maintenanceFor a temporary error or timeout, retry with the same idempotency key. For disabled service, ask support about API activation; check the status page.

For service availability, check system status. For an account or delivery issue, email support@relypost.com with your request ID and message UUID. Find requests in Developer → API request logs, including failures before queueing. Accepted emails also appear in Delivery logs. Do not include API keys, passwords or webhook secrets.

Using an app that asks for SMTP?

Open Organization → SMTP settings to get the public host, port, TLS mode, mailbox username and a separate send-only app password. Use those in your website, CRM or email client. A Bearer API key cannot authenticate to SMTP.

Open SMTP settings

Frequently asked questions

Does the RelyPost API support HTML emails?

Yes. POST /v1/emails accepts an html string of up to 500,000 characters. Include text for a plain-text alternative. Render your template in your application before sending; the API does not provide a hosted template endpoint.

Can I send attachments and inline images?

Yes. Reserve an upload, upload the bytes to the signed storage URL, and finalize it. Reference the ready file ID in attachments. For inline images, set cid on the attachment and use the matching cid: value in HTML. Up to 20 attachments are accepted, subject to message-size and plan limits.

Can I schedule an email?

Yes. Set scheduled_at to a UTC ISO 8601 timestamp ending in Z, no more than 365 days ahead. Save the exact payload and idempotency key for retries. There is no public cancellation or rescheduling endpoint.

Which programming languages and frameworks work?

Any server environment with HTTPS support can use the REST API. This guide includes cURL, Node.js fetch and Python standard-library examples; no RelyPost SDK is required. Keep keys on your backend, including when your frontend is a mobile app.

Does HTTP 202 mean the email was delivered?

No. HTTP 202 means the request was accepted into the queue. Read GET /v1/emails/{id} or receive webhooks for later outcomes. Destination acceptance does not prove inbox placement or that a person opened the email.

Are API access and SMTP credentials the same?

No. API requests use a scoped Bearer API key. External SMTP uses a sender address and a separate SMTP app password with the connection settings shown in Organization → SMTP settings. Both depend on account activation, sender authorization and applicable limits.

Can the API read inboxes, send campaigns or manage domains?

The public API sends transactional email and reads domain status and DNS details. It does not provide inbox reading, message listing, domain creation, hosted templates, campaign management or open/click tracking endpoints. Configure domains and senders in the workspace.

Where can I find pricing, limits and help?

See the public Pricing page for plans and Developer → Billing for your organization’s allowances. Defaults described here may be configured differently per deployment. For help, contact support@relypost.com with error.code and error.requestId, without sharing credentials.