Notification infrastructure you don’t have to write.
One API for email, push, and chat. Your provider credentials, your sending account, your rates.
curl -X POST "$RAY_API/send" \ -H "Authorization: Bearer $RAY_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "externalUserId": "user_8f21", "feed": { "title": "Your invoice is ready" }, "deliveries": [ { "channelConfigId": "cfg_push", "templateId": "tpl_invoice", "recipient": { "token": "eG9kd0…" } }, { "channelConfigId": "cfg_email", "templateId": "tpl_invoice", "recipient": { "email": "[email protected]" } }, { "channelConfigId": "cfg_slack", "templateId": "tpl_inv_slack", "recipient": {} } ] }'
202 Accepted { "sendId": "f1e2c7a0-…" }
Ever-expanding transports, on your own accounts.
Your SES identity, your FCM project, your SMTP host. Messages leave under your account, at your provider’s price. Ray takes no cut of a send.
Three steps, then it’s just an API call.
Connect a provider
Your SES keys, FCM service account, SMTP login, or bot token. Encrypted before it reaches the database.
Publish a template
Channel-shaped and versioned. Variables are detected when you save.
Call POST /send
Ray renders, queues, dispatches, retries with backoff, and records the outcome.
The unglamorous half of sending a message.
One call, several channels
One sendId, one feed entry, one delivery row per channel.
Retry-safe by header
Templates that can’t rewrite history
Every send stores the body it rendered. Editing later never changes what was received.
Escaping with no opt-out
No raw-output syntax, so a parameter cannot emit markup.
Delivery events you can verify
The shape you already handle from Stripe or GitHub.
A notification feed you don’t build
Pass your own user id, read that user’s history back. We hold no record of your users.
GET /notifications?externalUserId=user_8f21&limit=20 → { "items": [ { "title": "Your invoice is ready", "createdAt": "…" } ], "cursor": "…" }
Also included
- Scheduled sends
- Suppression lists fed by bounces and complaints
- Send priority, so a bulk blast can’t jump a password reset
- API keys scoped to read or write
- Cross-tenant reads that answer 404, never 403
- Click tracking with campaign grouping (Starter and above)
- An audit log of every configuration change (Scale and above)
We erase the message after 30 days, on every plan.
We don’t sell message data, share it for advertising, or train models on it. Read the privacy policy
Priced on what you send.
$0/ mo
- 25,000 notifications
- 10/s send rate
- 30-day delivery history
- 1 seat
$19/ mo
- 250,000 notifications
- 60/s send rate
- 90-day delivery history
- 3 seats
- Click tracking
$59/ mo
- 1,000,000 notifications
- 120/s send rate
- 365-day delivery history
- 10 seats
- Click tracking
- Delivery webhooks
$199/ mo
- 5,000,000 notifications
- 300/s send rate
- 730-day delivery history
- 25 seats
- Click tracking
- Delivery webhooks
- Audit log
Built for coding agents, and the code they write.
Connect the MCP server and your agent reads your channels, drafts and tests templates, and checks deliveries with the same scoped API key. The code it writes uses the TypeScript SDK.
MCP server
Hosted at ray-api.gege.mn/mcp. Use a read-only key to explore; sends need write.
claude mcp add --transport http ray https://ray-api.gege.mn/mcp \ --header "Authorization: Bearer $RAY_API_KEY"
TypeScript SDK
Zero dependencies. Node.js, Bun, Deno and edge runtimes.
npm install @gege-mn/ray import { Ray } from '@gege-mn/ray'; const ray = new Ray(process.env.RAY_API_KEY); await ray.send({ channelConfigId: 'cfg_email', templateId: 'tpl_welcome', recipient: { email: '[email protected]' }, });
Also for agents: ray-skills, every docs page as markdown, and llms.txt.
Send your first notification this afternoon.
Sign in with GitHub, point a channel config at a provider you already pay for, publish a template. The free plan covers 25,000 notifications a month and never asks for a card.