Quickstart — first email in 5 minutes

Send your first email with Sendoka. Three equivalent paths: curl, fetch (Node), Python.

At the end you'll have:

  • A verified sender domain (or the shared sandbox)
  • A live API key
  • A delivered email you can inspect in the dashboard

0. Sign up & grab a key

  1. Create an account at /signup. You land in a fresh org.
  2. Go to Dashboard → API Keys → New key.
  3. Pick Test mode for now — keys start with sok_test_… and don't hit real SES. Switch to sok_live_… once you've verified a domain.
  4. Copy the key. It's only shown once.
export SENDOKA_API_KEY="sok_test_..."

Sandbox: every org gets a sandbox.sendoka.com sender auto-provisioned, so you can send before verifying a real domain. Rate-limited; outbound mail only reaches verified test inboxes.

1. Send an email

curl

curl -sS https://api.sendoka.com/api/v1/emails \
  -H "Authorization: Bearer $SENDOKA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "you@sandbox.sendoka.com",
    "to": ["you+quickstart@example.com"],
    "subject": "Hello from Sendoka",
    "html": "<p>It works. <strong>Ship it.</strong></p>"
  }'

Response:

{
  "id": "msg_01HN...",
  "channel": "email",
  "status": "sent",
  "created_at": "2026-04-22T10:14:22.113Z"
}

Node (fetch)

const res = await fetch("https://api.sendoka.com/api/v1/emails", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SENDOKA_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    from: "you@sandbox.sendoka.com",
    to: ["you+quickstart@example.com"],
    subject: "Hello from Sendoka",
    html: "<p>It works. <strong>Ship it.</strong></p>",
  }),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
const msg = await res.json();
console.log(msg.id); // msg_...

Python

Use httpx or requests:

import os, httpx

r = httpx.post(
    "https://api.sendoka.com/api/v1/emails",
    headers={"Authorization": f"Bearer {os.environ['SENDOKA_API_KEY']}"},
    json={
        "from": "you@sandbox.sendoka.com",
        "to": ["you+quickstart@example.com"],
        "subject": "Hello from Sendoka",
        "html": "<p>It works. <strong>Ship it.</strong></p>",
    },
    timeout=30,
)
r.raise_for_status()
print(r.json()["id"])

2. Send an SMS (optional)

Same pattern, different endpoint.

curl -sS https://api.sendoka.com/api/v1/sms \
  -H "Authorization: Bearer $SENDOKA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+15551234567",
    "to": "+15557654321",
    "body": "Sendoka says hi"
  }'

In test mode, the provider call is simulated — you'll get back status: "sent" with a test_msg_… provider id and nothing hits SNS. Switch to a live key to actually deliver.

3. Watch it happen

  • Dashboard → Activity — real-time feed of sends, opens, clicks, bounces.
  • Dashboard → Messages — click into any msg_… for full headers, attachments, webhook fan-out status, timeline.
  • X-Request-Id on every response — paste into support tickets.

4. Handle errors

Every error response shares a shape. The code is stable and the request_id is reflected in the X-Request-Id header.

{
  "error": {
    "type": "validation_error",
    "code": "VALIDATION_ERROR",
    "message": "Subject is required",
    "request_id": "req_Hg8JpNvKXWg4f",
    "errors": [
      { "path": ["subject"], "code": "invalid_type", "message": "Required" }
    ]
  }
}

Full catalog: api/errors.md.

5. Next steps

Pick the track that matches where you're going:

Goal Read this next
Send with your own domain features/domains.md
Receive delivery events features/webhooks.md
Templates with variables api/templates.md
Schedule a future send api/scheduled-sends.md
Bulk / audience blast api/audiences.md
Multi-tenant SaaS on top features/platforms.md
Never double-send api/idempotency.md
Understand limits api/rate-limits.md

Troubleshooting

401 UNAUTHORIZED — key is missing, typo'd, revoked, or expired. Regenerate in the dashboard.

403 DOMAIN_NOT_ALLOWED_FOR_KEY — this key is scoped to specific domains and yours isn't on the list. Edit the key or send from an allowed domain.

422 VALIDATION_ERROR — inspect error.errors[] for the field that failed. Common: from not a valid email, to empty, neither html nor text nor template present.

429 USAGE_LIMIT_EXCEEDED — free plan hit its monthly cap. Upgrade or wait until the next cycle.

Email "sent" but never arrived — check Dashboard → Messages → for the bounce/complaint reason. Spam folder is usually the culprit during warmup.