API reference / Send a message

POST /v1/send

Send a message

Queues one message for delivery. The domain in from must be verified on your account; an unverified domain is rejected rather than sent unsigned.

Pass an Idempotency-Key header on anything you might retry. A repeated request with the same key returns the original response instead of sending twice, and carries Idempotent-Replay: true so you can tell the two apart. Keys are remembered for 24 hours and are scoped to your API key, so order-4471 means your order, not somebody else's. Reusing a key with a different body is a 400 — that is almost always a bug, and replaying the first message would hide it.

Headers

Authorization string required
Bearer followed by your API key.
Content-Type string required
application/json.
Idempotency-Key string optional
Up to 255 characters. Your own identifier for this send — an order id, a job id.

Body parameters

from string required
Sender address on a verified domain. Accepts addr@domain or Name <addr@domain>. The domain must match the one your key is scoped to.
to string | string[] required
One recipient or an array. Each accepts a bare address, Name <addr>, or { name, email }. Each recipient counts once against your monthly volume.
subject string required
Subject line.
html string optional
HTML body. Supply html, text, or both — at least one.
text string optional
Plain-text body. Sent as the alternative part when html is also present.
cc string | string[] optional
Copied recipients, same formats as to.
bcc string | string[] optional
Blind-copied recipients, same formats as to.
reply_to string | string[] optional
Where replies should go, if not the from address.
tags string[] optional
Labels carried into the event log and every webhook for this message.
headers object optional
Custom headers. Anything Pharos sets itself — authentication, routing, the standard message headers — is rejected with 400 rather than ignored, so you find out at the call rather than from a recipient.

Errors

CodeTypeMeaning
400 invalid_request A field is missing or malformed. error.field names it.
401 invalid_key The key is missing, wrong, revoked, or scoped to another domain.
403 domain_unverified The sending domain has not passed verification.
422 recipient_suppressed On your suppression list. Not sent, and not counted against your volume.
429 rate_limited Above 100 requests/minute. Retry-After says when to resume.
500 internal_error Our fault, not yours. The request was well-formed. Safe to retry.
502 upstream_error The sending service was unreachable or answered in a way we do not recognise. Safe to retry.

Every error has the same shape: type is the stable machine-readable part, message is for a human reading logs and may be reworded, field appears when one field is at fault, and docs points at the page that explains it.

Response

idstring
Identifier for this message. Use it in the event log and match it against webhooks.
statusstring
queued. A 202 means accepted for delivery, not delivered — delivery outcomes arrive by webhook and in the event log.
dkimstring
signed. Messages leave signed; an unverified domain cannot send at all.
dmarcstring
aligned. The signing domain matches the from domain the recipient sees.
RequestcURL
curl -X POST https://api.pharos.email/v1/send \
  -H "Authorization: Bearer $PHAROS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4471" \
  -d '{
    "from": "receipts@yourapp.com",
    "to": "customer@example.com",
    "subject": "Your receipt from Acme",
    "html": "<p>Thanks for your order.</p>",
    "tags": ["receipt"]
  }'
Response202 Accepted
{
  "id":     "msg_2f8Kq1Zx",
  "status": "queued",
  "dkim":   "signed",
  "dmarc":  "aligned"
}
Error response403 Forbidden
{
  "error": {
    "type":    "domain_unverified",
    "message": "yourapp.com is not verified",
    "field":   "from",
    "docs":    "/docs/domains"
  }
}