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:

Shell
export SOONER_API_KEY="sn_..."

Every request needs the bearer header. Requests with a body also need the JSON content type:

Shell
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

Shell
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>"
  }'
JSON
{ "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:

Shell
curl -X POST https://api.sooner.dev/emails \
  -H "Authorization: Bearer $SOONER_API_KEY" \
  -H "Content-Type: application/json" \
  -d @email.json

Read the result

Add -i to see the status line and headers, or -w to print only the status code:

Shell
curl -s -o response.json -w "%{http_code}\n" https://api.sooner.dev/emails \
  -H "Authorization: Bearer $SOONER_API_KEY"
StatusMeaning
200Success. Creating domains and API keys returns 201.
400, 422The request is invalid. Read the name and message in the body.
401, 403The key is missing, invalid or not allowed to do this.
404The resource does not exist in this project.
409An idempotency conflict.
429Rate limit or quota. Read the retry-after header.
451The team is suspended.
5xxA problem on our side. Retry with backoff.

Errors always have the same JSON body:

JSON
{ "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:

Shell
curl --fail-with-body -sS https://api.sooner.dev/usage \
  -H "Authorization: Bearer $SOONER_API_KEY" || echo "request failed"

Other common calls

Shell
# 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:

Shell
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')
done

See 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:

Shell
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.json

The 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:

Shell
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 | base64

The output should match the part of svix-signature after v1,. In application code, use a library instead. See Verify signatures.