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

bash
# 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.