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
- Create an account at
/signup. You land in a fresh org. - Go to Dashboard → API Keys → New key.
- Pick Test mode for now — keys start with
sok_test_…and don't hit real SES. Switch tosok_live_…once you've verified a domain. - Copy the key. It's only shown once.
export SENDOKA_API_KEY="sok_test_..."
Sandbox: every org gets a
sandbox.sendoka.comsender 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-Idon 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 →