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

Getting started

Ray documentation

Ray is a notification delivery API for email, push, SMS, Slack, Discord, Telegram and webhooks. Core concepts and a 5-minute quickstart.

View as Markdownllms.txtAPI reference

Ray is a multi-tenant notification delivery API: one HTTP endpoint (POST /send) delivers transactional notifications over email (Amazon SES or any SMTP relay), mobile push (Firebase Cloud Messaging), SMS (Twilio or sendsms.mn), Slack, Discord, Telegram and your own HTTPS webhook. Ray is a relay, so you bring your own provider credentials and Ray handles templates, idempotency, fan-out, retries, rate limiting, suppression, signed delivery webhooks, a per-user notification feed, click tracking and delivery logs. (Ray the notification API is not related to the Ray distributed computing framework.)

  • Dashboard: https://ray.gege.mn
  • API base URL: https://ray-api.gege.mn
  • Live API reference: https://ray-api.gege.mn/docs
  • OpenAPI 3.1 spec: https://ray-api.gege.mn/openapi.json
  • TypeScript SDK: npm install @gege-mn/ray, see TypeScript SDK
  • MCP server for AI agents: https://ray-api.gege.mn/mcp, see MCP server
  • Agent skills: https://github.com/gege-mn/ray-skills

Every page in these docs is also available as raw markdown by appending .md to its URL (for example https://ray.gege.mn/docs/sending.md), and all pages are concatenated at https://ray.gege.mn/llms-full.txt.

Core concepts

Concept What it is
Workspace Your tenant. Every API key, channel, template, send and webhook belongs to exactly one workspace. Data in another workspace is invisible (it returns 404).
API key A bearer token (ck_live_...) created in the dashboard with read and/or write scope. See Authentication.
Channel config A set of provider credentials stored encrypted in Ray (an SES identity, an SMTP relay, an FCM service account, a Slack webhook URL, ...). Referenced by channelConfigId. Created in the dashboard under Channels.
Template Reusable, versioned message content for one template kind (email_html, fcm_basic, slack_text, discord_text, telegram_text, webhook_json) with {{variable}} placeholders. See Templates.
Inline content Channel-shaped content sent directly in POST /send instead of a templateId. Rendered through the same pipeline as a template.
Recipient The per-send delivery target. Its shape depends on the channel: { "email": "..." } for email, { "deviceToken": "..." } for push, { "chatId": "..." } for Telegram, { "phoneNumber": "..." } for SMS, {} for Slack, Discord and webhooks.
Params The JSON values substituted into {{variables}} at render time.
Send One accepted POST /send call, identified by sendId. A send has one delivery row per recipient (fan-out) or per channel (multi-channel). See Sending.
Feed entry An in-app notification for one of your end users, keyed by your own externalUserId and read back with GET /notifications. See Status and feeds.
Suppression An address Ray will not send to on a given channel config, added automatically from SES hard bounces and complaints.
Delivery webhook A signed HTTPS callback Ray POSTs to your server when deliveries finish. See Webhooks.

Channels

Channel Channel type Template kind Recipient Guide
Amazon SES email ses_email email_html { "email", "name"?, "cc"?, "bcc"?, "from"?, "replyTo"?, "headers"?, "attachments"? } Email (SES)
SMTP email smtp_email email_html same as SES Email (SMTP)
Firebase Cloud Messaging fcm_push fcm_basic { "deviceToken" } or { "topic" } Push (FCM)
Slack incoming webhook slack_webhook slack_text {} Slack
Discord webhook discord_webhook discord_text {} Discord
Telegram bot telegram_bot telegram_text { "chatId" } Telegram
Twilio SMS twilio_sms sms_text { "phoneNumber" } (E.164) SMS (Twilio)
sendsms.mn SMS sendsms_mn sms_text { "phoneNumber" } (8 digits) SMS (sendsms.mn)
HTTPS webhook generic_webhook webhook_json {} Webhook

An email_html template works with both SES and SMTP channel configs, and an sms_text template works with both Twilio and sendsms.mn.

Quickstart

This sends your first email in about five minutes. You need provider credentials for one channel; the fastest options are a Slack or Discord webhook URL, or SMTP credentials you already have.

1. Create an API key

  1. Sign in at https://ray.gege.mn (GitHub sign-in) and create a workspace.
  2. Open API keys, click create, keep both read and write scopes checked.
  3. Copy the key. It is shown in full only once.
export RAY_API_KEY="ck_live_..."   # paste your key

Check it works:

curl https://ray-api.gege.mn/me \
  -H "Authorization: Bearer $RAY_API_KEY"
{ "tenantId": "0f9e8d7c-6b5a-4c3d-9e2f-1a0b9c8d7e6f", "apiKeyId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "scopes": ["read", "write"] }

2. Configure a channel

In the dashboard open Channels, click New channel, pick a provider and paste its credentials. For email over SMTP that is host, port, username, password and a from address; each channel guide lists exactly what to collect from the provider.

Then find the new config's id with the API:

curl https://ray-api.gege.mn/channels \
  -H "Authorization: Bearer $RAY_API_KEY"
{
  "channels": [
    {
      "id": "5d2e8f1a-6b7c-4d8e-9f0a-2b3c4d5e6f7a",
      "name": "transactional",
      "kind": "smtp_email",
      "templateKind": "email_html",
      "recipientSchema": { "type": "object", "properties": { "email": { "type": "string", "format": "email" } }, "required": ["email"] }
    }
  ]
}

recipientSchema is the JSON Schema of the recipient object that channel expects (abbreviated above).

3. Send a notification

The quickest send uses inline content, so no template is needed yet:

curl -X POST https://ray-api.gege.mn/send \
  -H "Authorization: Bearer $RAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "channelConfigId": "5d2e8f1a-6b7c-4d8e-9f0a-2b3c4d5e6f7a",
    "content": {
      "subject": "Hello from Ray, {{name}}",
      "bodyHtml": "<p>Hi {{name}}, this is your first Ray notification.</p>",
      "bodyText": "Hi {{name}}, this is your first Ray notification."
    },
    "params": { "name": "Ada" },
    "recipient": { "email": "[email protected]" }
  }'
{ "sendId": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d" }

The 202 Accepted response means the send is queued. Delivery happens asynchronously.

For a Slack channel the same call is:

curl -X POST https://ray-api.gege.mn/send \
  -H "Authorization: Bearer $RAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channelConfigId": "7c8d9e0f-1a2b-4c3d-9e4f-5a6b7c8d9e0f",
    "content": { "text": "Deploy finished for *{{service}}*" },
    "params": { "service": "billing-api" },
    "recipient": {}
  }'

The same email send with the TypeScript SDK (npm install @gege-mn/ray):

import { Ray } from "@gege-mn/ray";

const ray = new Ray(process.env.RAY_API_KEY);

const { sendId } = await ray.send(
  {
    channelConfigId: "5d2e8f1a-6b7c-4d8e-9f0a-2b3c4d5e6f7a",
    content: {
      subject: "Hello from Ray, {{name}}",
      bodyHtml: "<p>Hi {{name}}, this is your first Ray notification.</p>",
      bodyText: "Hi {{name}}, this is your first Ray notification.",
    },
    params: { name: "Ada" },
    recipient: { email: "[email protected]" },
  },
  { idempotencyKey: "welcome-ada" },
);

Or with plain fetch, no dependency:

const res = await fetch("https://ray-api.gege.mn/send", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RAY_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    channelConfigId: "5d2e8f1a-6b7c-4d8e-9f0a-2b3c4d5e6f7a",
    content: {
      subject: "Hello from Ray, {{name}}",
      bodyHtml: "<p>Hi {{name}}, this is your first Ray notification.</p>",
      bodyText: "Hi {{name}}, this is your first Ray notification.",
    },
    params: { name: "Ada" },
    recipient: { email: "[email protected]" },
  }),
});
if (res.status !== 202) throw new Error(`Ray send failed: ${res.status} ${await res.text()}`);
const { sendId } = (await res.json()) as { sendId: string };

4. Check delivery

curl https://ray-api.gege.mn/sends/6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d \
  -H "Authorization: Bearer $RAY_API_KEY"
{
  "sendId": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
  "createdAt": "2026-09-14T09:30:00.000Z",
  "aggregate": { "total": 1, "delivered": 1 },
  "rows": [ { "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f", "status": "delivered", "providerMessageId": "<[email protected]>", "providerError": null } ],
  "nextCursor": null
}

Rows are abbreviated. A row moves from pending to delivered, failed_terminal or suppressed; see Status and feeds.

Next steps

  • Move content into a versioned template and send with templateId.
  • Send to many recipients or several channels at once: Sending.
  • Make retries safe with Idempotency.
  • Get told when deliveries finish with Webhooks.
  • Use the typed client in Node.js, Bun, Deno or edge runtimes: TypeScript SDK.
  • Using an AI coding agent: connect the MCP server, then see Ray for AI agents.
  • Limits and error codes: Rate limits and quotas, Errors.
NextAuthentication

On this page

  • Core concepts
  • Channels
  • Quickstart
  • 1. Create an API key
  • 2. Configure a channel
  • 3. Send a notification
  • 4. Check delivery
  • Next steps
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