Search the docs
Find a page, a section, or an endpoint.
Sending and receiving
Sending from an address you own, with attachments and replies.
from has to be an address you own, and in_reply_to keeps the conversation
together. Sends go through the same abuse checks and the same daily cap as the
web composer — there is one set of rules, and the API is not a way around it.
Attachments
Upload the file, then name its id in the send. Two calls, because a ten-megabyte PDF is thirteen megabytes of base64 and putting it in the JSON body would make every proxy between you and us carry it that way.
curl https://pidgeon.ai/api/v1/attachments \
-H "Authorization: Bearer $PIDGEON_API_KEY" \
-F identity_id=0c939bd7-2d69-4150-a2ac-de994f0e4887 \
-F file=@invoice.pdfAn upload expires in a day. It is a file no message refers to yet, so the window is long enough for a retry after a failed send and short enough that a crashed script does not leave its files with us.
An id belongs to the address that uploaded it. Naming somebody else's is
a 404, which is also what a 404 means everywhere else here: not yours, or
not there, and we do not say which.
A send takes all the ids or none. If one has expired the whole call is refused and nothing is consumed, so the ones that are still good can be used in the retry. Half a message is worse than a refusal.
Two sizes, both from your plan: one file, and all the files on one message. Going over either is refused before the message is built rather than truncated after, because a silently shortened attachment is worse than a send that did not happen.
An HTML body
Pass html beside text, not instead of it.
curl https://pidgeon.ai/api/v1/messages \
-H "Authorization: Bearer $PIDGEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "from": "receipts@acme.com", "to": "customer@example.com",
"subject": "Your receipt", "text": "Thanks.",
"html": "<p>Thanks.</p>" }'text stays required. A message with no plain-text part renders as nothing
in a client that refuses HTML, and sending one is a deliverability decision
nobody should make by leaving a field out.
Send a reply
curl https://pidgeon.ai/api/v1/messages -H "Authorization: Bearer $PIDGEON_API_KEY" -H "Content-Type: application/json" -d '{
"from": "hello@yourdomain.com",
"to": "billing@supplier.com",
"subject": "Re: Invoice 4417",
"text": "Received, thank you.",
"in_reply_to": "af08b0a1-284f-464a-a36e-8e15bb7c2eb8"
}'in_reply_to sets the In-Reply-To and References headers, which is what
makes the reply land in the same conversation in the recipient's client as
well as in yours.
Rejected, not raised
Recipients that fail a check come back in rejected with a reason, and the
rest are sent. A suppressed address, a domain that does not resolve, or a
recipient over a limit does not lose the other four.
{
"data": {
"id": "2a41309b-eefd-447c-a844-7c10a2469d6a",
"thread_id": "463ec73a-699f-4ea8-a8fa-bb736ae1601e",
"rejected": [{ "address": "gone@example.com", "reason": "suppressed" }]
}
}An address that hard-bounced or reported spam is suppressed automatically and stays suppressed until it is cleared. Sending to it again is refused rather than attempted, because the reputation cost falls on the domain rather than on the message.
A recipient your own rules forbid is not this. That refuses the whole
send — 403 with blocked when a rule on the address says no, and
not_permitted when its send policy does — and nothing goes to anybody. The
two are separated on purpose: rejected is the provider declining an address
at hand-off, which can leave a send partly done and is worth reading for.
Your own rules are checked before anything leaves, and dropping one recipient
silently is how send the invoice to accounts and to Marcus reaches one of
them.
What limits a send
Three things, and the effective limit is the lowest of them: your plan's daily allowance, the account's trust tier, and the per-minute API rate limit. A new domain starts with a small allowance and earns its way up as its mail is delivered and read — nobody can make a new domain trustworthy overnight, and a large limit on day one is a reputation burnt on day two.