Skip to navigation

Quickstart

From API key to delivered message in three requests

1. Create a workspace key

Open the Keys page in the dashboard and create a key. You’ll get an rray_live_... (or rray_test_...) secret — store it safely, it is shown exactly once.

A workspace key is bound to one workspace, so it needs no workspace header. Every request authenticates with a Bearer header:

export RRAY_API_KEY="rray_live_..."

2. Send a message

curl -X POST https://api.rray.app/v1/messages \
-H "Authorization: Bearer $RRAY_API_KEY" \
-H "Idempotency-Key: order-1042-receipt" \
-H "Content-Type: application/json" \
-d '{
"from": "receipts@yourdomain.com",
"to": ["customer@example.com"],
"subject": "Your receipt",
"html": "<h1>Thanks!</h1>",
"text": "Thanks!"
}'

Use a from address on a sending domain you’ve added in the dashboard — that is what lets us DKIM-sign the message. The API won’t reject an unknown domain, but unsigned mail is very likely to be rejected by the recipient’s provider. to is an array; cc, bcc, reply_to and attachments are optional.

A 202 Accepted returns the message id and nothing else — the message has been accepted for delivery, not yet delivered:

{
"id": "msg_01M4ANAVD7EYMHY0PWSH5PBDJD"
}

Two headers worth understanding before production:

  • Idempotency-Key — makes retries safe; see Idempotency.
  • RRay-Workspace-Id — required only when you authenticate with a platform key. A workspace key rejects it; see Credentials.

Send responses also carry RRay-Quota-Limit, RRay-Quota-Remaining and RRay-Quota-Reset, reporting your plan’s monthly and daily allowance as structured-field dictionaries.

To send up to 100 messages in one request, POST /v1/messages/batch with a JSON array of the same message objects. Every ceiling the API enforces — batch size, attachment budget, rate limits, retention — is listed on Limits & facts.

3. Track delivery

Statuses progress accepted → queued → delivered, or end in bounced, deferred or failed — Message lifecycle explains what each one means and what to do about it. Poll one message:

curl https://api.rray.app/v1/messages/msg_01M4ANAVD7EYMHY0PWSH5PBDJD \
-H "Authorization: Bearer $RRAY_API_KEY"
{
"id": "msg_01M4ANAVD7EYMHY0PWSH5PBDJD",
"object": "message",
"status": "delivered",
"from": "receipts@yourdomain.com",
"subject": "Your receipt",
"deliveries": [
{
"address": "customer@example.com",
"role": "to",
"status": "delivered"
}
],
"attachments": [],
"workspace_id": "ws_01KYA3AWSZGCB9YMNKM5AQBA8G",
"created_at": "2026-10-07T07:48:02.607020Z",
"events": [
{
"id": "evt_01M4ANAVDNW22V8M1EHEPQJ8DM",
"object": "delivery_event",
"type": "accepted",
"address": "customer@example.com",
"created_at": "2026-10-07T07:48:02.590140Z"
},
{
"id": "evt_bfa8d9d5a3eeaafd9bf798230b2cfaed",
"object": "delivery_event",
"type": "queued",
"address": "customer@example.com",
"created_at": "2026-10-07T07:48:02Z"
},
{
"id": "evt_54e3166c7b31ddc76f699de9ef0efff7",
"object": "delivery_event",
"type": "delivered",
"address": "customer@example.com",
"created_at": "2026-10-07T07:48:02Z"
}
]
}

Or list recent messages with a filter:

curl "https://api.rray.app/v1/messages?status=delivered&limit=20" \
-H "Authorization: Bearer $RRAY_API_KEY"

List responses are cursor-paginated — follow pagination.next_cursor for older messages, pagination.prev_cursor for newer ones (how it works). You can also filter by id, recipient and created_at.

If something goes wrong

Errors are flat application/problem+json bodies with a stable code to match on and a request_id to quote to support:

{
"type": "https://api.rray.app/errors/message.not_found",
"title": "Message not found",
"status": 404,
"code": "message.not_found",
"category": "not_found",
"detail": "The message does not exist or is not visible to your credential.",
"request_id": "df7b848fabd945889820b257e004c894",
"instance": "/v1/messages/msg_00000000000000000000000000"
}

The full catalog lives at GET /errors and in the Errors guide.