HTTP API
One POST per email, from any language or framework.
Create an API key under API keys in the dashboard. Keys are hashed at rest and shown once; scope them per project.
curl https://sendfeather.com/api/emails \
-H "Authorization: Bearer ulk_live_..." \
-H "Content-Type: application/json" \
-d '{
"from": "[email protected]",
"to": ["[email protected]"],
"subject": "Your receipt",
"html": "<h1>Thanks, Maya!</h1>"
}'Authentication
Send the key as an Authorization: Bearer header. A key only ever sends as its own project, so one leaked key cannot touch another workspace.
The payload
- from — must be an address on a verified sending domain
- to, cc, bcc — arrays of recipients
- subject, html, text — the message itself
- reply_to — optional
- attachments — base64 content with a filename and content type
- headers — allow-listed custom headers (List-Unsubscribe is welcome)
- tags — searchable metadata on the message
Responses
A 202 means the message is accepted and queued; the response carries its id. A 422 names the problem — an unverified sender, a suppressed recipient, a malformed address. A 429 means a limit was reached; the message was not sent.
Managing projects, domains, and mailboxes
Platforms onboard their own customers over the management API: create a project per customer, add their domain, poll until it verifies, then create mailboxes and SMTP users — no dashboard visits. It needs a workspace integration key, which only the Sendfeather team issues; ask for one, and the flow below works end to end.
# One project per customer.
curl https://sendfeather.com/api/projects \
-H "Authorization: Bearer ulk_live_..." \
-d "name=Customer One"
# Add their domain; the response carries every DNS record to publish.
curl https://sendfeather.com/api/projects/12/domains \
-H "Authorization: Bearer ulk_live_..." \
-d "domain=customer.example.com"
# Poll it while DNS verifies: status pending -> verified, and
# approval_status tells you when it may send.
curl https://sendfeather.com/api/projects/12/domains/customer.example.com \
-H "Authorization: Bearer ulk_live_..."
# Create the customer's mailbox; the password comes back once.
curl https://sendfeather.com/api/projects/12/mailboxes \
-H "Authorization: Bearer ulk_live_..." \
-d "local_part=support" -d "domain=customer.example.com"- GET/POST /api/projects — list and create projects (one per customer).
- GET/POST /api/projects/{id}/domains — add a domain and read its records, status, and review state back; GET /api/projects/{id}/domains/{domain} polls one.
- POST /api/projects/{id}/domains/{domain}/publish — write the records through the workspace's connected Cloudflare account, when one is connected.
- GET/POST /api/projects/{id}/mailboxes — create mailboxes on a verified domain; the response carries the address, the password once, and the IMAP/SMTP settings.
- GET/POST /api/projects/{id}/smtp-users — create sending credentials; the password comes back once.
- GET/POST /api/projects/{id}/suppressions — the do-not-send list, including a bulk import (up to 1,000 addresses per request) for bringing unsubscribes and hard bounces over from your old provider.
Domains are approved on arrival for trusted workspaces; others wait for a quick review before their first send, and approval_status says which. One workspace owns a domain platform-wide, so adding a domain another workspace claimed answers 409.