Guides

Sendara for AI agents

Let an LLM or coding agent send email through Sendara — one endpoint, idempotent retries, typed errors, and a sandbox to rehearse in. Email is the only GA send channel today; SMS and voice OTP for agents are on the roadmap.

Why Sendara is built for agents

Autonomous agents need an API they can call confidently without a human in the loop. Sendara is shaped for exactly that:

  • One endpoint. Every message — email today, SMS and more later — goes through a single POST /v1/send. The agent learns one call, not a surface area.
  • Idempotent by design. Each send carries an idempotency_key; a retry with the same key returns the original result instead of sending again. Agents that retry on timeout never double-send.
  • Predictable, typed JSON errors. Failures come back as { "error": { "code", "message", "status" } } with a stable machine-readable code — easy for an agent to branch on (from_not_verified, recipient_suppressed, …) without parsing prose.
  • Simple API-key auth. One header — Authorization: Bearer sk_live_…. No OAuth dance, no token refresh for the agent to manage.
  • Sandbox & test sends. A sk_test_… key simulates delivery (never billed, no real mail) so an agent can rehearse a workflow end to end before it touches a real inbox.
The whole API is one verb away: give the agent an API key and the send_email tool below, and it can send mail in a single tool call. Everything else (templates, broadcasts, domains) is additive — see the SDKs and API reference.

Give your agent the docs

We publish an LLM-readable dump of these docs at https://sendara.dev/llms.txt. It is a compact, plain-text description of the base URL, auth, the send body, every channel, error codes, and the sandbox — written to fit in a model's context window.

Two ways to use it:

  • Drop it in context.Fetch the file and paste it into the agent's system prompt or a project knowledge file (e.g. a CLAUDE.md or an OpenAI Assistant instruction). The model then knows how to call Sendara without you hand-writing the spec.
  • Index it for retrieval.Point a retrieval tool or vector store at the URL so the agent can pull the relevant section on demand — handy when you don't want the full file resident in every turn.
# Fetch the LLM-readable docs and hand them to your agent's context.
curl -s https://sendara.dev/llms.txt -o sendara-llms.txt

Minimal send

The simplest possible send, in every official SDK plus raw HTTP. This mirrors /docs/sending and /docs/sdks exactly — construct a client with your API key, then call emails.send. The SDK attaches an idempotency_keyfor you when you don't pass one.

Python reserves from, so the helper takes from_ (keyword-only). In Go every call takes a context.Context first and params are passed by value. The sender must be on a verified domain (or omit it for the shared sender).

Tool / function calling

Expose Sendara to a model as a single send_email tool. The schema below is the Anthropic shape (name, description, input_schema). It maps directly to OpenAI function calling — wrap it as { "type": "function", "function": { name, description, "parameters": input_schema } } and the JSON Schema body is identical. Either way the model returns the tool name and a JSON object of arguments.

send_email.tool.json
{
  "name": "send_email",
  "description": "Send a transactional email to a single recipient through Sendara. Returns the created message id.",
  "input_schema": {
    "type": "object",
    "properties": {
      "to": {
        "type": "string",
        "description": "Recipient email address."
      },
      "subject": {
        "type": "string",
        "description": "Subject line."
      },
      "html": {
        "type": "string",
        "description": "HTML body of the email."
      },
      "from": {
        "type": "string",
        "description": "Sender address on a verified domain. Omit to use the shared sender."
      }
    },
    "required": ["to", "subject", "html"]
  }
}

When the model emits a tool call, route its name and arguments into a handler that calls the Sendara SDK and returns the result for the model to read on the next turn.

Keep the tool surface tiny. Most agents only need send_email; add a send_email_template tool (passing templateId + templateVars) only when you want copy to live in Sendara instead of the model's output. See templates.

Idempotency & retries

Autonomous agents retry — on a timeout, a tool-runner restart, or a replanned step. Without a stable key, each retry is a fresh send and your user gets duplicate mail. Pass a deterministic idempotency_key derived from the logical action (an order id, a recipient + intent) so a retried call is a guaranteed no-op.

idempotent.ts
// Derive the key from the action, not from the clock or a random value —
// the same logical send must produce the same key on every retry.
await sendara.emails.send({
  from: "[email protected]",
  to: "[email protected]",
  subject: "Your receipt",
  html: "<p>Thanks!</p>",
  idempotencyKey: `receipt-${orderId}`, // same order ⇒ at most one email
});
Reusing a key with a different payload returns 409 — keys are bound to the request they first succeeded with. A clean retry of the same send returns the original result. More on the SDK retry behavior (429, 5xx, network) in the SDKs guide.

Dry-run safely

Let an agent rehearse without billing or real mail. Hand it a test key (sk_test_…) and every send is simulated — not billed, no real delivery — but still fires your webhooks, so the full workflow runs end to end. Address the simulator inbox to force an outcome: delivered@, bounced@, or complained@ on any domain.

sandbox.ts
const sandbox = new Sendara(process.env.SENDARA_TEST_KEY!); // sk_test_…

await sandbox.emails.send({
  from: "[email protected]",
  to: "[email protected]", // simulates a hard bounce + bounced webhook
  subject: "Agent dry run",
  html: "<p>Not really sent.</p>",
});

To have the agent send a real email to one of your own verified test recipients (free, capped per day), set testSend — the SDK forwards it as test_send: true:

test-send.ts
await sendara.emails.send({
  from: "[email protected]",
  to: "[email protected]", // must be a verified test recipient
  subject: "Agent UAT — real delivery",
  html: "<p>This one actually arrives.</p>",
  testSend: true,
});
Test-send guards surface as 409 conflicts you can branch on by code. See sandbox & test sends for the full flow.

Coming next: SMS & voice OTP for agents

Email is the only generally available send channel today. SMS and voice OTP are on the roadmap, not yet released — calling POST /v1/send with channel set to sms or voice currently returns 422 channel_not_enabled. Once those channels ship, the plan is for the same single endpoint to carry them: you'll flip channel to sms or voice with a { phone_number }destination and a one-time code in the payload, and your agent will be able to verify a user over the same API key and the same idempotency guarantees it already uses for email. We're building this email-first; watch the sending guide and llms.txt as the agent SMS path lands.