RavenRelayDocs
Dashboard ↗

Get started

Complete integration guide

From domain verification to safe development sends, production activation and verified delivery webhooks.

1. Create a project and verify a domain

Create your workspace and a development project in the dashboard. Connect a domain whose DNS you control. Publish the exact DKIM CNAME records and custom MAIL FROM MX and SPF records shown in Domains at your DNS provider, including Namecheap. MAIL FROM uses a separate raven subdomain. Avoid creating duplicate SPF records at the same hostname. Recheck verification until the dashboard reports the domain ready. DMARC is recommended but an absent policy does not block verification.

2. Create a server-side development key

Create an email:send key in API Keys. Store the one-time secret privately as RAVENRELAY_API_KEY on your server. rr_test_ keys send real mail only to exact verified recipient domains connected to the same project. Use a mailbox you own on that domain for testing. All To, CC and BCC addresses must be allowed; otherwise the entire request is rejected with test_recipient_not_allowed. Each key has 15 accepted requests per minute and 1,000 recipient sends per UTC month. Do not put keys in browser bundles, public repositories or agent prompts.

3. Send from your server

HTTP request
curl https://api-production-1c6a.up.railway.app/v1/emails \  -H "Authorization: Bearer $RAVENRELAY_API_KEY" \  -H 'Content-Type: application/json' \  -H 'Idempotency-Key: welcome-user-123' \  -d '{    "from": "hello@example.com",    "to": ["user@example.com"],    "subject": "Welcome",    "text": "Hello from RavenRelay"  }'

Replace example.com with your verified domain and the recipient with your own allowed mailbox. A 202 response contains id, status and test_mode. queued means accepted for asynchronous dispatch, not delivered to an inbox. suppressed means no email will be sent. Use one stable Idempotency-Key per logical send and exactly the same payload when retrying it. Keys are printable ASCII, at most 128 characters; reuse with a different payload returns idempotency_conflict. Handle HTTP failures explicitly. For rate_limit_exceeded, wait the seconds stated in the error before retrying. Do not automatically resend delivery_unknown outcomes: provider acceptance may have happened.

4. Read outcomes

Read a message
curl https://api-production-1c6a.up.railway.app/v1/emails/EMAIL_ID -H "Authorization: Bearer $RAVENRELAY_API_KEY"

GET /v1/emails accepts limit (1–100, default 50), cursor, status and recipient filters. Follow next_cursor until it is null. Recipient filtering matches To addresses. A send key reads emails it created; an email:read key reads its project. sent means the delivery provider accepted the message; delivered means the receiving mail server accepted it. Neither guarantees inbox placement. The read API omits bodies and BCC. Message bodies are retained for seven days.

5. Receive signed webhooks

Create a public HTTPS endpoint on port 443 in the dashboard and subscribe to the events your application needs. Store its one-time signing secret privately. Read the original request bytes before JSON middleware. Verify RavenRelay-Timestamp and RavenRelay-Signature using HMAC-SHA256 with the literal UTF-8 secret and timestamp + a dot + the raw body. Compare signatures in constant time and reject timestamps more than five minutes away from your clock. The signature format is v1=<hex>. Only then parse and process the event.

Node signature verification (Web Request)
import { createHmac, timingSafeEqual } from "node:crypto";​export async function verifiedEvent(request: Request, secret: string) {  const timestamp = request.headers.get("RavenRelay-Timestamp") ?? "";  const signature = request.headers.get("RavenRelay-Signature") ?? "";  if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300)    throw new Error("Invalid timestamp");  if (!/^v1=[a-f0-9]{64}$/.test(signature)) throw new Error("Invalid signature");  const raw = Buffer.from(await request.arrayBuffer());  const expected = createHmac("sha256", secret).update(timestamp + ".").update(raw).digest();  const received = Buffer.from(signature.slice(3), "hex");  if (!timingSafeEqual(expected, received)) throw new Error("Invalid signature");  return JSON.parse(raw.toString("utf8"));}

Deduplicate the verified payload id in durable storage and acknowledge with 2xx after committing it. RavenRelay-Event-Id carries the stable ID; retries keep the same ID and body but use a fresh timestamp/signature. Events can arrive out of order. Non-2xx, timeouts and network failures retry after 1 minute, 5 minutes, 15 minutes, 1 hour, 6 hours and 24 hours: seven attempts total. Redirects are not followed. Development deliveries also generate external webhooks with data.test_mode=true. Legacy simulated messages do not. Manual replay is not currently available.

6. Respect suppressions

Hard bounces and spam complaints automatically block the recipient across the workspace. Owners and administrators can also add manual blocks. Any blocked To, CC or BCC recipient prevents the whole request from sending. Test the flow using bounce@simulator.amazonses.com and complaint@simulator.amazonses.com from an activated production project; these addresses are outside your development allow list. Send once, wait for feedback, then resend to see suppressed. Remove a real complaint block only after resolving the cause and confirming permission to contact the recipient.

7. Activate production

Request workspace activation and complete the administrator review. Create a production project, connect and verify its sender domains, then create a rr_live_ key. Switch your server configuration to that key and project. Production recipient domains are unrestricted, subject to workspace activation, suppressions and provider rules. Keep development and production secrets separate.

8. Budget and handle errors

The free allowance is 5,000 accepted recipient sends per workspace per UTC month across all projects and domains. To, CC and BCC count; real development deliveries also count. Unlimited domains and no added branding are included. There is no RavenRelay daily quota, but request rate limits and delivery provider limits apply. See project usage in Usage and aggregate usage through View workspace usage on the Usage page.

8. Budget and handle errors
FailureAction
401 invalid_api_keyCheck server configuration and key revocation.
403 forbidden / production_not_enabledCheck permissions and workspace activation.
422 domain_not_verified / test_recipient_not_allowedVerify the exact project sender and allowed development recipient domains.
409 idempotency_conflictKeep the payload unchanged or create a key for a genuinely new send.
429 rate_limit_exceededWait the number of seconds in the message and retry with the same idempotency key.
429 quota_exceededCheck the key and workspace monthly allowances; wait for the UTC reset or contact support.
413 message_too_largeKeep the complete JSON request within 10 MiB. More than 50 total recipients returns validation_error.
RavenRelay documentation. Examples describe the current implementation.