Ray
PricingDocsSign inStart free
Browse the docs

Getting started

Ray documentationAuthenticationUsing Ray with AI coding agentsMCP serverTypeScript SDK

Guides

Sending notificationsIdempotencyTemplatesScheduling and test sendsStatus and feedsClick trackingDelivery webhooks

Channels

Email with Amazon SESEmail with SMTPPush notifications with Firebase Cloud MessagingSlack notificationsDiscord notificationsTelegram notificationsSMS with TwilioSMS with sendsms.mnHTTPS webhook channel

Reference

Rate limits and quotasErrors

Getting started

Ray documentationAuthenticationUsing Ray with AI coding agentsMCP serverTypeScript SDK

Guides

Sending notificationsIdempotencyTemplatesScheduling and test sendsStatus and feedsClick trackingDelivery webhooks

Channels

Email with Amazon SESEmail with SMTPPush notifications with Firebase Cloud MessagingSlack notificationsDiscord notificationsTelegram notificationsSMS with TwilioSMS with sendsms.mnHTTPS webhook channel

Reference

Rate limits and quotasErrors

Reference

Rate limits and quotas

Ray API rate limits per plan, monthly notification quotas, provider pacing per channel, send priority, retries and request size limits.

View as Markdownllms.txtAPI reference

Four separate mechanisms shape throughput in Ray: API rate limits (how fast you can call https://ray-api.gege.mn), the monthly notification quota (how much you can send), provider pacing (how fast queued messages leave for each provider) and send priority (which queued messages go first).

API rate limits

Two token buckets apply to every request:

  1. Per client IP, before authentication. This throttles unauthenticated traffic and key guessing.
  2. Per workspace, after authentication, shared by all API keys of the workspace. The size depends on the plan.
Plan Sustained requests per second Burst
Free 10 20
Starter 60 120
Pro 120 240
Scale 300 600

A bucket holds up to Burst tokens and refills at the sustained rate. Each request takes one token, whatever it does: one POST /send with 1000 targets is one request. To send many notifications, prefer fan-out over one request per recipient.

Responses carry these headers:

Header Meaning
RateLimit-Limit Sustained requests per second of the bucket.
RateLimit-Remaining Whole tokens left.
RateLimit-Reset Seconds until the bucket is full again.

Because two buckets apply, treat these headers as a hint. The authoritative signal is 429:

HTTP/1.1 429 Too Many Requests
Retry-After: 1
RateLimit-Limit: 10
RateLimit-Remaining: 0
RateLimit-Reset: 1
Content-Type: application/json

{ "error": "rate_limit_exceeded", "message": "rate limit exceeded", "retry_after_seconds": 1 }

Wait Retry-After seconds, then retry:

async function rayFetch(path: string, init: RequestInit = {}, maxAttempts = 5): Promise<Response> {
  for (let attempt = 1; ; attempt++) {
    const res = await fetch(`https://ray-api.gege.mn${path}`, {
      ...init,
      headers: { Authorization: `Bearer ${process.env.RAY_API_KEY}`, ...init.headers },
    });
    if (res.status !== 429 || attempt >= maxAttempts) return res;
    const seconds = Number(res.headers.get("Retry-After")) || 2 ** attempt;
    await new Promise((r) => setTimeout(r, seconds * 1000));
  }
}

Retrying POST /send is only safe with an Idempotency-Key (a 429 itself means nothing was sent, but a timeout does not).

Monthly quota

Each plan includes a number of notifications per calendar month (UTC). See https://ray.gege.mn/pricing for plans and prices.

Plan Notifications per month At the limit
Free 25,000 Hard cap. Sends that would exceed it are refused with 402.
Starter 250,000 Soft cap. Sends keep working; responses are flagged over quota.
Pro 1,000,000 Soft cap.
Scale 5,000,000 Soft cap.

There is no metered overage billing. Going over a soft cap shows an upgrade prompt; it does not block sending.

What counts as one notification:

  • each delivery row created by POST /send (one per recipient in fan-out, one per channel in multi-channel), and
  • each feed entry.

What does not count:

  • suppressed recipients,
  • test sends (they have a separate daily allowance),
  • idempotent replays,
  • requests that fail (4xx or 5xx).

Rows that are accepted and later fail at the provider (failed_terminal) do count.

POST /send responses carry quota headers:

Header Meaning
RayQuota-Limit Monthly quota.
RayQuota-Used Notifications counted this month, including this request.
RayQuota-Remaining max(0, limit - used).
RayQuota-Status over when a soft-capped plan is past its quota. Absent otherwise.

On the Free plan, a request that would cross the quota is refused as a whole (a 1000-target send with 500 notifications left is rejected, not partially sent):

HTTP/1.1 402 Payment Required
RayQuota-Limit: 25000
RayQuota-Used: 24500

{ "error": "quota_exceeded", "message": "monthly notification quota exceeded", "limit": 25000, "used": 24500 }

Read current usage at any time:

curl https://ray-api.gege.mn/billing/subscription \
  -H "Authorization: Bearer $RAY_API_KEY"
{
  "plan": "starter",
  "subscription": {
    "provider": "polar",
    "plan": "starter",
    "status": "active",
    "currentPeriodEnd": "2026-10-01T00:00:00.000Z",
    "cancelAtPeriodEnd": false
  },
  "usage": { "monthlyQuota": 250000, "used": 18342, "remaining": 231658 }
}

plan uses internal keys: free, starter, growth (Pro) and business (Scale). subscription is null without a paid subscription.

Provider pacing

Past the API, queued deliveries leave at a rate each provider tolerates. A large send is never rejected for provider limits; it is queued and drains at the channel's pace.

Channel Pacing Configurable
ses_email ratePerMinute, default 840 (14 per second), max 1600 Yes, in the channel config. Set it to your SES maximum send rate times 60.
smtp_email ratePerMinute, default 840, max 1600 Yes. Match your relay's limit.
telegram_bot ratePerMinute, default 1500, max 1600 Yes.
twilio_sms ratePerMinute, default 300, max 1600 Yes. Twilio queues anything above a sender's own throughput.
sendsms_mn ratePerMinute, default 300, max 1600 Yes.
slack_webhook 1 message per second No (Slack's limit per incoming webhook).
fcm_push None No.
discord_webhook None (Discord 429s are retried) No.
generic_webhook None No.

Pacing is keyed by credential, not by channel config: two SES configs with the same region and access key share one pace, as do two configs using the same SMTP host, port and username, the same Telegram bot token, the same Twilio Account SID and Auth Token, the same sendsms.mn API key and token, or the same Slack webhook URL. For fully independent throughput (for example marketing apart from transactional mail), use a separate provider credential.

A 10,000-recipient email at 840 per minute takes about 12 minutes to drain.

Send priority

All sends share one queue. Without priority, a 10,000-recipient newsletter queued first would delay a password-reset email queued a second later. Set priority on POST /send:

  • "high" (default): transactional messages such as sign-in codes, receipts and alerts.
  • "low": bulk and marketing.

Workers always take high-priority deliveries before low-priority ones, so transactional sends jump ahead of a queued bulk send. Priority changes ordering, not the provider pace; if both share a credential they still share its pacing.

{
  "channelConfigId": "9f1c2a7e-3b4d-4e5f-8a6b-1c2d3e4f5a6b",
  "templateId": "1d2e3f4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
  "targets": [{ "recipient": { "email": "[email protected]" } }],
  "priority": "low"
}

Retries and timeouts

What Behavior
Provider call timeout 10 seconds per request, 5 seconds to connect.
Retryable provider failure Row becomes failed_retryable and is retried up to 5 times with exponential backoff from 30 seconds.
Row stuck retrying Moved to failed_terminal an hour after its last attempt.
Delivery webhook to your endpoint 10 second timeout, up to 10 retries with exponential backoff from 30 seconds, disabled after 10 consecutive failures.

Each channel guide lists which provider errors are terminal and which are retried.

Request size limits

Endpoint Max body
POST /send 45 MiB (room for base64 email attachments)
Every other endpoint 1 MiB

Larger bodies get 413 with { "error": "payload_too_large", "message": "request body exceeds the size limit" }.

Other limits

Limit Value
targets[] per send 1000
deliveries[] per send 10
Test sends per UTC day Free 50, Starter 250, Pro 500, Scale 500
Email attachments 20 per message, about 10 MiB each, about 25 MiB total
Email cc and bcc 50 each (SES allows 50 recipients per message in total)
Email replyTo 1 to 10 addresses
Email headers 15 per message (the SES limit); value 1 to 995 characters, name plus value at most 996
PreviousHTTPS webhook channelNextErrors

On this page

  • API rate limits
  • Monthly quota
  • Provider pacing
  • Send priority
  • Retries and timeouts
  • Request size limits
  • Other limits
Ray

Notification delivery for teams that already have providers.

Product

PricingDocsAPI referenceMCP serverTypeScript SDKAgent skillsllms.txt

Legal

TermsPrivacyRefunds

Contact

[email protected]

© 2026 L’Atelier Gege LLC