SDKs
cURL
Call the Sooner API from the command line or any HTTP client, with headers, status codes, pagination and error handling.
The Sooner API is plain HTTPS and JSON, so any HTTP client works. This page shows the conventions with cURL. Every example in the docs can be run this way.
Set up
Export your key once per shell session:
export SOONER_API_KEY="sn_..."Every request needs the bearer header. Requests with a body also need the JSON content type:
curl https://api.sooner.dev/domains \
-H "Authorization: Bearer $SOONER_API_KEY"Keep keys out of shell history and scripts you commit. See Authentication.
Send an email
curl -X POST https://api.sooner.dev/emails \
-H "Authorization: Bearer $SOONER_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: welcome-user-123" \
-d '{
"from": "Acme <hello@mail.yourcompany.com>",
"to": ["ada@example.com"],
"subject": "Welcome",
"html": "<p>Thanks for signing up.</p>"
}'{ "id": "01jcz8w3f2x9k4m7n6p5q2r1s0" }Send html or text. Raw HTTP callers cannot use the react field. See Send an email.
To keep a long body out of the command line, put it in a file and pass -d @email.json:
curl -X POST https://api.sooner.dev/emails \
-H "Authorization: Bearer $SOONER_API_KEY" \
-H "Content-Type: application/json" \
-d @email.jsonRead the result
Add -i to see the status line and headers, or -w to print only the status code:
curl -s -o response.json -w "%{http_code}\n" https://api.sooner.dev/emails \
-H "Authorization: Bearer $SOONER_API_KEY"| Status | Meaning |
|---|---|
200 | Success. Creating domains and API keys returns 201. |
400, 422 | The request is invalid. Read the name and message in the body. |
401, 403 | The key is missing, invalid or not allowed to do this. |
404 | The resource does not exist in this project. |
409 | An idempotency conflict. |
429 | Rate limit or quota. Read the retry-after header. |
451 | The team is suspended. |
5xx | A problem on our side. Retry with backoff. |
Errors always have the same JSON body:
{ "statusCode": 422, "message": "`to`: Invalid input", "name": "validation_error" }See Errors for every name.
Use --fail-with-body (cURL 7.76 or later) to make the command exit with a non-zero code on an HTTP error while still printing the body, which is useful in scripts:
curl --fail-with-body -sS https://api.sooner.dev/usage \
-H "Authorization: Bearer $SOONER_API_KEY" || echo "request failed"Other common calls
# Retrieve an email (full_access key)
curl https://api.sooner.dev/emails/01jcz8w3f2x9k4m7n6p5q2r1s0 \
-H "Authorization: Bearer $SOONER_API_KEY"
# Create a contact
curl -X POST https://api.sooner.dev/contacts \
-H "Authorization: Bearer $SOONER_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "ada@example.com", "first_name": "Ada" }'
# Check usage
curl https://api.sooner.dev/usage \
-H "Authorization: Bearer $SOONER_API_KEY"Contacts, segments, topics, broadcasts, templates, suppressions and webhooks are documented in the API reference.
Paginate with jq
List endpoints take limit and after and return has_more. This loop prints every email ID:
after=""
while true; do
page=$(curl -s "https://api.sooner.dev/emails?limit=100${after:+&after=$after}" \
-H "Authorization: Bearer $SOONER_API_KEY")
echo "$page" | jq -r '.data[].id'
[ "$(echo "$page" | jq -r '.has_more')" = "true" ] || break
after=$(echo "$page" | jq -r '.data[-1].id')
doneSee Pagination.
Retry on rate limits
When the API returns 429, wait for the retry-after header. This script retries a send up to five times:
for attempt in 1 2 3 4 5; do
status=$(curl -s -D headers.txt -o body.json -w "%{http_code}" -X POST https://api.sooner.dev/emails \
-H "Authorization: Bearer $SOONER_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: digest-2026-11-14" \
-d @email.json)
[ "$status" = "200" ] && break
name=$(jq -r '.name' body.json)
if [ "$status" = "429" ] && [ "$name" != "monthly_quota_exceeded" ]; then
wait=$(grep -i '^retry-after:' headers.txt | tr -d '\r' | awk '{print $2}')
sleep "${wait:-$((2 ** attempt))}"
elif [ "$status" -ge 500 ]; then
sleep $((2 ** attempt))
else
break
fi
done
cat body.json
rm -f headers.txt body.jsonThe same Idempotency-Key on every attempt guarantees the email is sent at most once. See Idempotency and Rate limits.
Test a webhook signature
cURL cannot verify a signature for you, but you can check one from the shell. Given the three headers and the raw body saved to body.json, compute the expected signature with openssl:
secret="whsec_..."
id="msg_..." # svix-id header
timestamp="1763116262" # svix-timestamp header
key=$(printf '%s' "${secret#whsec_}" | base64 -d | xxd -p -c 256)
printf '%s.%s.%s' "$id" "$timestamp" "$(cat body.json)" \
| openssl dgst -sha256 -mac HMAC -macopt hexkey:"$key" -binary | base64The output should match the part of svix-signature after v1,. In application code, use a library instead. See Verify signatures.