DOCUMENTATION
Quickstart
Every request authenticates with Authorization: Bearer sk_…. In mock mode the dev key sk_test_local works with zero setup; production keys are configured via SHARKMAIL_API_KEYS.
1 — Send an email
curl -X POST https://your-sharkmail.app/api/v1/emails \
-H "Authorization: Bearer sk_test_local" \
-H "Content-Type: application/json" \
-d '{"to":"customer@example.com","subject":"Hi","text":"Hello!"}'
# → 201 {"id":"...","conversationId":49034330,"from":"hello@yourdomain.com"}
# Over your plan's rate limit or sending allowance you get 429:
# → 429 {"error":"rate_limit_exceeded","retryAfterSeconds":42} + Retry-After header
# → 429 {"error":"monthly_limit_reached","plan":"free"} (or daily_limit_reached)Replies to that email are routed back to your inbox automatically and threaded into the same conversation — both on your side and in the recipient’s mail client.
2 — Check usage
curl https://your-sharkmail.app/api/v1/usage \
-H "Authorization: Bearer sk_test_local"
# → {"month":"2026-07","emails":420,"day":"2026-07-07","dailyEmails":12,
# "plan":"marketing",
# "limits":{"monthlyEmails":10000,"dailyEmails":null,
# "apiRateLimit":{"limit":120,"windowMs":60000}},
# "overageEmails":0,"overageUsd":0}Paid tiers keep sending past the included volume and meter the excess at $1 per 1,000 (overageEmails / overageUsd); the free tier hard-stops at its monthly and daily allowance.
Templates
Store a message once, send it with per-recipient variables. Any {{token}} in the subject or body is replaced from variables at send time; unknown tokens render empty.
# Create a template — {{tokens}} render at send time
curl -X POST https://your-sharkmail.app/api/v1/templates \
-H "Authorization: Bearer sk_test_local" \
-H "Content-Type: application/json" \
-d '{"name":"welcome","subject":"Welcome, {{firstName}}",
"text":"Hi {{firstName}} — glad you are here."}'
# → 201 {"template":{"id":"tpl_...","name":"welcome",...}}
# → 409 {"error":"template_cap_exceeded"} past your plan's cap (3 free / 1,000 paid)
# Send it — variables fill the tokens
curl -X POST https://your-sharkmail.app/api/v1/emails \
-H "Authorization: Bearer sk_test_local" \
-H "Content-Type: application/json" \
-d '{"to":"customer@example.com","templateId":"tpl_...",
"variables":{"firstName":"Sam"}}'
# GET /api/v1/templates list
# GET /api/v1/templates/{id} read one
# PATCH /api/v1/templates/{id} partial update (name/subject/text/html)
# DELETE /api/v1/templates/{id} removeContacts, lists & segments
Contacts are unlimited on every plan and upsert by email address. Lists are explicit groups; segments are rule sets evaluated against your contacts on demand, so membership is always current.
# Upsert by email — 201 created, 200 updated (fields merge, lists union)
curl -X POST https://your-sharkmail.app/api/v1/contacts \
-H "Authorization: Bearer sk_test_local" \
-H "Content-Type: application/json" \
-d '{"email":"sam@example.com","firstName":"Sam",
"lists":["lst_..."],"fields":{"plan":"pro"}}'
# GET /api/v1/contacts list everyone
# GET /api/v1/contacts?list=lst_... one list's members
# DELETE /api/v1/contacts?email=sam@example.com remove
# Bulk import — CSV with an email column; extra columns become custom fields
curl -X POST https://your-sharkmail.app/api/v1/contacts/import \
-H "Authorization: Bearer sk_test_local" \
-H "Content-Type: text/csv" \
--data-binary $'email,firstName,plan\nsam@example.com,Sam,pro'
# → {"imported":1,"updated":0,"errors":[]}
# Lists
curl -X POST https://your-sharkmail.app/api/v1/lists \
-H "Authorization: Bearer sk_test_local" \
-H "Content-Type: application/json" -d '{"name":"Newsletter"}'
# GET /api/v1/lists · DELETE /api/v1/lists?id=lst_...
# Segments — rules are AND-ed and evaluated live, nothing is materialized
curl -X POST https://your-sharkmail.app/api/v1/segments \
-H "Authorization: Bearer sk_test_local" \
-H "Content-Type: application/json" \
-d '{"rules":[{"field":"plan","op":"eq","value":"pro"},
{"field":"lists","op":"contains","value":"lst_..."}]}'
# ops: eq | neq | contains | exists | not_existsSuppressions
Hard bounces, complaints, and unsubscribes land on the suppression list automatically and block future sends with a 409. Inspect, add to, or clear the list yourself:
curl https://your-sharkmail.app/api/v1/suppressions \
-H "Authorization: Bearer sk_test_local"
# → {"suppressions":[{"email":"gone@example.com","reason":"bounce",...}]}
# Add one manually (reason: bounce | complaint | unsubscribe | manual)
curl -X POST https://your-sharkmail.app/api/v1/suppressions \
-H "Authorization: Bearer sk_test_local" \
-H "Content-Type: application/json" \
-d '{"email":"never@example.com","reason":"manual"}'
# Remove — sending to the address resumes
curl -X DELETE "https://your-sharkmail.app/api/v1/suppressions?email=never@example.com" \
-H "Authorization: Bearer sk_test_local"
# → 204Automations
Automations run a sequence of steps — send_template and wait — when a contact is added (contact.added) or joins a list (contact.subscribed). They fire in the background; the API call that triggered them never waits.
# Multi-step email flow, triggered by contact events (Marketing plan)
curl -X POST https://your-sharkmail.app/api/v1/automations \
-H "Authorization: Bearer sk_test_local" \
-H "Content-Type: application/json" \
-d '{
"name": "Welcome series",
"trigger": "contact.added",
"steps": [
{ "type": "send_template", "templateId": "tpl_..." },
{ "type": "wait", "ms": 86400000 },
{ "type": "send_template", "templateId": "tpl_..." }
],
"enabled": true
}'
# trigger: contact.added | contact.subscribed (optional listId narrows to one list)
# → 403 {"error":"automations_require_marketing_plan"} on other plans
# GET /api/v1/automations · GET/PATCH/DELETE /api/v1/automations/{id}SMTP relay
Already have an app that speaks SMTP? Run pnpm smtp and point your existing mail config at the relay — no code changes. Every message is authenticated by your API key, forwarded through the same pipeline as the REST API, and metered identically.
# Any SMTP client works — password is your API key. host: localhost # wherever the relay runs port: 2525 username: anything password: sk_test_local # e.g. with swaks: swaks --server localhost:2525 --auth-user x --auth-password sk_test_local \ --from you@yourdomain.com --to customer@example.com \ --header "Subject: Via SMTP" --body "Sent through the relay."
Webhooks
Register a URL under Settings → Webhooks and Sharkmail POSTs you replies and delivery events as they happen. Verify the signature before trusting a payload.
// POST to your registered URL, signed:
// X-Sharkmail-Event: conversation.reply | email.delivered | email.opened |
// email.clicked | email.bounced | email.complained |
// email.unsubscribed
// X-Sharkmail-Signature: hex(hmacSha256(secret, rawBody))
{
"event": "conversation.reply",
"conversationId": 49034330,
"channel": "email",
"from": "customer@example.com",
"preview": "Sounds great — tell me more!",
"timestamp": "2026-07-06T19:04:25.835Z"
}Marketing campaigns
Under Campaigns in the dashboard: paste a recipient list, write once, send. Each recipient gets an individual conversation (replies come back as separate threads, not one blast thread), delivery and open counts roll up per campaign, and previously bounced or complaining addresses are skipped automatically.
API reference
POST
/api/v1/emails
Send an email — inline subject/text/html, or templateId + variables. Auto-threads per recipient; pass conversationId to control grouping.
GET
/api/v1/usage
Metered usage, plan limits, and accrued overage for the presented key.
GET POST
/api/v1/templates
List / create reusable templates with {{variable}} tokens. Creation is capped by plan.
GET PATCH DELETE
/api/v1/templates/{id}
Read, partially update, or delete one template.
GET POST DELETE
/api/v1/contacts
List (?list= filters to one list), upsert by email, or remove (?email=) contacts.
POST
/api/v1/contacts/import
Bulk import a text/csv body with an email column; extra columns become custom fields.
GET POST DELETE
/api/v1/lists
List, create { name }, or delete (?id=) contact lists.
POST
/api/v1/segments
Evaluate { rules } against your contacts on demand and return the matching set.
GET POST DELETE
/api/v1/suppressions
List suppressed addresses with reasons, add one, or remove one (?email=).
GET POST
/api/v1/automations
List / create event-triggered email flows (Marketing plan).
GET PATCH DELETE
/api/v1/automations/{id}
Read, update (including enabled), or delete one automation.
POST
/api/campaigns
Batch-send a campaign (dashboard session auth). One conversation per recipient.
POST
/api/webhooks
Register a webhook URL (dashboard session auth). Returns the signing secret once.
Sending from your own domain
Add your domain in the dashboard under Domains. Sharkmail creates the identity and gives you three DKIM CNAME records to add at your DNS host. The dashboard polls verification for you; once verified, every send goes out as hello@yourdomain.com. Replies keep flowing through Sharkmail’s reply routing, so your inbound wiring never changes.
Architecture in one paragraph
Outbound email rides AWS SES with delivery/open/click events; inbound lands on a Cloudflare Email Worker that pushes each message straight to the server (no storage hop). Your dashboard holds one SSE connection to the Sharkmail server — vendor traffic stays flat no matter how many users or tabs are open, which is what keeps the whole receive side inside Cloudflare’s free plan from 10 users to 1,000.