Email infrastructure for AI agents
Let your AI agents
run real email
conversations.
Send transactional email, receive replies, manage threads, run campaigns and automations, through API or MCP, with per-key limits and approvals built in.
Pay for emails sent. Never for contacts stored.
- 3,000 emails a month free
- Inbound included
- No charge without an upgrade you choose


One URL, every MCP client
Domain to first send
From zero to sent without an email archaeology degree.
Add your domain, publish the records we return, verify it, send. SendRaven keeps transactional and marketing mail on separate identities, so a newsletter cannot trash the reputation of your password resets.
- 01
Add your domain
POST /v1/domainsCreates
mail.acme.comandnews.acme.comand returns the DNS records to publish. - 02
Verify
POST /v1/domains/:id/verifyAnswers verified, or names the record that is still missing.
- 03
Send
POST /v1/emailsWith an idempotency key. Schedule it in plain words:
"in 3 days".
Two front doors
REST when you're coding.
MCP when your agent is.
Call the API from your backend, or connect SendRaven to Claude Code, Cursor, Codex, ChatGPT and other MCP clients. Both reach the same account and obey the same permissions and limits. MCP is not the differentiator. What the agent is allowed to do through it is.
One URL, every MCP client
The server runs remotely and signs in with OAuth, so connecting it is pasting a URL. The tools are the same in every client.
- Remote, over Streamable HTTP
- OAuth, or an API key as a Bearer header
- Named for what an agent is trying to do
- A held send reports awaiting review
pick your client
claude mcp add --transport http sendraven https://mcp.sendraven.ai/mcpThe first tool call opens a browser to sign in and pick a workspace. Nothing else to configure.
https://mcp.sendraven.ai/mcpClaude Desktop and claude.ai. Paste the URL as the remote MCP server, then authorize once with OAuth. Available on paid plans.
https://mcp.sendraven.ai/mcpTurn on developer mode under Advanced first. Paste the URL, choose OAuth, and the tools appear in the composer's connector list.
{
"mcpServers": {
"sendraven": { "url": "https://mcp.sendraven.ai/mcp" }
}
}Project-level, or ~/.cursor/mcp.json for every project. Cursor prompts to authorize on first use. Add to Cursor
codex mcp add sendraven --url https://mcp.sendraven.ai/mcpWrites the server into ~/.codex/config.toml. Run codex mcp login sendraven to authorize.
gemini mcp add --transport http sendraven https://mcp.sendraven.ai/mcpAdds it to ~/.gemini/settings.json. /mcp inside a session lists the tools once it has connected.
code --add-mcp '{"name":"sendraven","type":"http","url":"https://mcp.sendraven.ai/mcp"}'Copilot agent mode. The same entry can live in .vscode/mcp.json under "servers" to ship with a repo.
{
"mcpServers": {
"sendraven": { "serverUrl": "https://mcp.sendraven.ai/mcp" }
}
}Windsurf uses serverUrl where other clients use url. Refresh the MCP panel after saving.
POST /v1/emails
Plain JSON over HTTPS, so there is no SDK to install, pin, or wait on for the next endpoint. Every sample is the same send.
- Idempotency-Key on every request
- thread_id comes back with the id
- Scheduling in plain words
- 402 when out of plan, not 429
curl -X POST https://api.sendraven.ai/v1/emails \
-H "Authorization: Bearer $SENDRAVEN_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"from": "Acme <hi@mail.acme.com>",
"to": "maya@acme.com",
"subject": "Your invoice for August",
"html": "<p>Hi Maya, it is attached.</p>"
}'
# 202 {"id": "…", "status": "queued", "thread_id": "…"}Everything the API does is in /openapi.json, written by hand so the descriptions say which mistakes are expensive.
const res = await fetch("https://api.sendraven.ai/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SENDRAVEN_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
from: "Acme <hi@mail.acme.com>",
to: "maya@acme.com",
subject: "Your invoice for August",
html: "<p>Hi Maya, it is attached.</p>",
}),
});
const email = await res.json();
// { id: "…", status: "queued", thread_id: "…" }Built-in fetch, Node 18 and later. The same code runs unchanged in Deno, Bun, and edge runtimes.
import os, uuid, requests
res = requests.post(
"https://api.sendraven.ai/v1/emails",
headers={
"Authorization": f"Bearer {os.environ['SENDRAVEN_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"from": "Acme <hi@mail.acme.com>",
"to": "maya@acme.com",
"subject": "Your invoice for August",
"html": "<p>Hi Maya, it is attached.</p>",
},
)
email = res.json()
# {"id": "…", "status": "queued", "thread_id": "…"}requests shown; httpx and urllib work the same way, as does any framework's HTTP client.
body, _ := json.Marshal(map[string]string{
"from": "Acme <hi@mail.acme.com>",
"to": "maya@acme.com",
"subject": "Your invoice for August",
"html": "<p>Hi Maya, it is attached.</p>",
})
req, _ := http.NewRequest("POST", "https://api.sendraven.ai/v1/emails", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("SENDRAVEN_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
// 202 and { "id": "…", "status": "queued" }Standard library only; uuid is github.com/google/uuid.
require "net/http"
require "json"
require "securerandom"
uri = URI("https://api.sendraven.ai/v1/emails")
req = Net::HTTP::Post.new(uri, {
"Authorization" => "Bearer #{ENV["SENDRAVEN_API_KEY"]}",
"Content-Type" => "application/json",
"Idempotency-Key" => SecureRandom.uuid,
})
req.body = {
from: "Acme <hi@mail.acme.com>",
to: "maya@acme.com",
subject: "Your invoice for August",
html: "<p>Hi Maya, it is attached.</p>",
}.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
email = JSON.parse(res.body)
# {"id" => "…", "status" => "queued"}Net::HTTP from the standard library. In Rails, the same call from a job is the whole integration.
$ch = curl_init("https://api.sendraven.ai/v1/emails");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("SENDRAVEN_API_KEY"),
"Content-Type: application/json",
"Idempotency-Key: " . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
"from" => "Acme <hi@mail.acme.com>",
"to" => "maya@acme.com",
"subject" => "Your invoice for August",
"html" => "<p>Hi Maya, it is attached.</p>",
]),
]);
$email = json_decode(curl_exec($ch), true);
// ["id" => "…", "status" => "queued"]The curl extension, or Laravel's Http::withToken() facade with the same headers.
var body = """
{"from": "Acme <hi@mail.acme.com>",
"to": "maya@acme.com",
"subject": "Your invoice for August",
"html": "<p>Hi Maya, it is attached.</p>"}
""";
var req = HttpRequest.newBuilder(URI.create("https://api.sendraven.ai/v1/emails"))
.header("Authorization", "Bearer " + System.getenv("SENDRAVEN_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
var res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
// res.body(): {"id": "…", "status": "queued"}java.net.http, in the JDK since 11. No client library to add to the build.
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("SENDRAVEN_API_KEY"));
http.DefaultRequestHeaders.Add("Idempotency-Key", Guid.NewGuid().ToString());
var res = await http.PostAsJsonAsync("https://api.sendraven.ai/v1/emails", new {
from = "Acme <hi@mail.acme.com>",
to = "maya@acme.com",
subject = "Your invoice for August",
html = "<p>Hi Maya, it is attached.</p>",
});
// 202 Accepted and {"id": "…", "status": "queued"}System.Net.Http.Json, in .NET since 5.
let res = reqwest::Client::new()
.post("https://api.sendraven.ai/v1/emails")
.bearer_auth(std::env::var("SENDRAVEN_API_KEY")?)
.header("Idempotency-Key", uuid::Uuid::new_v4().to_string())
.json(&serde_json::json!({
"from": "Acme <hi@mail.acme.com>",
"to": "maya@acme.com",
"subject": "Your invoice for August",
"html": "<p>Hi Maya, it is attached.</p>",
}))
.send()
.await?;
// 202 and {"id": "…", "status": "queued"}reqwest with the json feature, plus uuid and serde_json.
Same key, same limits, same suppression list on every door.
Guardrails on the key
Whichever door an agent comes through, it holds a key, not a workspace. So the cap, the allowlist and the approval hold live on the key and are checked on every send.
- Daily cap counts scheduled and held sends
- Allowlist checked per recipient
- Approval hold: drafted, then a person releases it
- A key may not edit its own limits
{
"name": "support-agent",
"scopes": ["emails:send", "threads:read"],
"daily_send_limit": 200,
"allowed_recipients": ["@acme.com"],
"requires_approval": true
}Checked on every send, before anything is persisted. A workspace quota trips after the damage; these trip before.
403 { "error": { "type": "recipient_not_allowed" } }
429 { "error": { "type": "daily_limit" } }
202 { "status": "pending_approval",
"approval_id": "apr_4c…" }The third is not an error. The message is drafted and waits for a person at GET /v1/approvals, and llms.txt tells a model exactly that, so it reports awaiting review instead of retrying in a loop.
Readable by agents
An agent can read this site, and the API, before it has a key.
The description documents are unauthenticated, and the errors are written for a model to act on.
claude mcp add --transport http sendraven https://mcp.sendraven.ai/mcpRemote server over Streamable HTTP. Signs in with OAuth, or takes an API key as a Bearer header.
/llms.txtWhat SendRaven is, which call to reach for, which errors are worth retrying, and the calls that cannot be undone.
/openapi.jsonWritten by hand rather than generated, so the descriptions say what a schema cannot: which mistakes are expensive.
402, not 429Out of plan is not too fast. A client that backs off on 429 would retry a 402 forever;
GET /v1/usagelets an agent say so before a large batch.
One email stack
One email stack.
Fewer tabs. Fewer bills.
Fewer weird edge cases.
Password resets, newsletters, onboarding sequences, product updates and support replies all belong to the same product. SendRaven keeps apart what should be apart, like sending reputation, without turning it into separate subscriptions.
Transactional email
Receipts, login links, notifications, security mail.
Send transactional emailCampaigns
Audiences, segments, previews, A/B tests and broadcasts.
Explore campaignsAutomations
Sequences started by an API call, a new contact or an application event.
Build an automationInbound and threads
Receive replies and continue the conversation.
How inbound worksTopics and preferences
Unsubscribe from one kind of email without disappearing from everything.
Set up topicsTemplates
Reusable email with variables, escaped on substitution.
Browse templatesSuppressions
Bounces, complaints and opt-outs respected across the whole system.
How suppressions workWebhooks
Signed delivery events, in real time, when your application needs them.
See webhook events
Pricing without the contact tax
We charge for email.
Not for having
an address book.
Store a thousand contacts or a million. Your bill changes when you send email, not when your audience grows. A contact is a row in a database. We don't charge rent for rows.
A million contacts who receive zero emails cost $0, because zero emails were sent.
Your database grew? Congrats. Your email bill didn't.
Compare every planFree
3,000
emails free
Every month, no expiry. Sending pauses at the limit, so Free never bills you. A payment method activates sending and is never charged on Free.
Standard
One meter$19
a month, from
50,000 emails to start, then automatic volume pricing: $109 at 250K, $379 at 1M. No tier to pick.
Enterprise
Custom
above 3 million
Negotiated volume rates, dedicated sending, an SLA and net terms. Self-service sending never stops at the line.
Unlimited contacts. $0 per contact. Always.
Inbound included. Replies are never metered.
One plan. Transactional and marketing on the same meter.
One meter
What the same month costs.
| SendRaven | $35.00 |
|---|---|
| Resend | $35.00 |
| Postmark | $115.00 |
| SendRaven | $109.00 |
|---|---|
| Resend | $170.00 |
| Postmark | $245.00 |
| SendRaven | $379.00 |
|---|---|
| Resend | $650.00 |
| Postmark | $775.00 |
| SendRaven | $0 |
|---|---|
| Resend | $650 marketing plan |
| Postmark | n/a |
Competitor figures are the lowest published price for each volume, overage included, checked 16 September 2026. Ours is the Standard plan at that volume. Full pricing, the comparisons with Resend and Postmark, and where the Resend gap comes from.
See the full comparisonFAQ
Questions about SendRaven
Answered in the first sentence. Each one has a longer page behind it.
What is SendRaven?
SendRaven is email infrastructure for AI agents and the applications they run in: an API and MCP server that sends transactional email and campaigns, receives replies as threads, runs automations, and manages contacts, topics and templates, with limits and human approval on the API key an agent holds. A product by the team of Common Ninja.
Can AI agents receive and reply to email with SendRaven?
Yes. Inbound replies are joined to the message they answer on its Message-ID and stored as a thread, with the quoted history stripped. An agent polls GET /v1/threads?awaiting_reply=true for conversations that need an answer, and replies with reply_to_message_id so the threading headers are set for it.
How is SendRaven different from Resend?
SendRaven adds conversation state and credential-level safety to the email API. Resend is excellent developer email and also ships inbound, broadcasts, topics, automations and an MCP server. SendRaven's differences are a thread object with an awaiting-reply query and stripped reply text, a daily cap, recipient allowlist and approval hold on the API key, and one plan for transactional and marketing mail priced per email sent rather than by contacts.
Does SendRaven charge per contact?
No. SendRaven charges for emails sent, and contacts are $0. There is one meter on every plan, so growing an audience does not change the bill until you mail it.
Can SendRaven handle transactional and marketing email?
Yes, on one API and one plan. Adding a domain creates two sending identities, mail. for transactional and news. for marketing, each with its own reputation, so a complaint spike on a newsletter cannot stop a password reset.
Can I require approval before an AI agent sends email?
Yes. Create the agent's API key with requires_approval and every send it makes becomes a draft held for a person. The call returns status pending_approval, which is not an error; a person releases or rejects it from the dashboard or the API, and an undecided draft expires after 72 hours.
Can I limit who an AI agent emails?
Yes. An API key can carry allowed_recipients, a list of full addresses or whole domains, and a daily_send_limit. Both are checked on every send before anything is stored, a send outside the allowlist answers 403 recipient_not_allowed, and a key cannot edit its own limits.
Does SendRaven support MCP?
Yes. SendRaven runs a remote MCP server that signs in with OAuth or takes an API key, and publishes the same server on npm as @sendraven/mcp. It calls the same REST API, so an agent connected over MCP has exactly the permissions and limits of its key.
What happens when someone replies to an automation?
The sequence stops for that person. Automations end an enrolment on a reply by default (exit_on_reply), and also on an unsubscribe, a hard bounce or complaint, a topic opt-out, or a tag rule. Out-of-office and other automatic replies do not count as a reply.
Is inbound email included?
Yes, on every plan, including Free. Received replies are not metered; only emails you send count toward the plan's 3,000 free or included emails.

Still have questions?
Read the docs
You send a raven. It comes back.
Give your agent a return address.
Start with 3,000 emails a month. Inbound included. Unlimited contacts. Human approval when you want it.

