Sending
Getting mail out, and what can stop it.
curl -X POST "https://api.carlyemail.com/v0/inboxes/$INBOX_ID/messages/send" \
-H "Authorization: Bearer $CARLYEMAIL_API_KEY" \
-H 'content-type: application/json' \
-d '{
"to": ["someone@example.org"],
"cc": [],
"subject": "Hello",
"text": "Plain body",
"html": "<p>HTML body</p>"
}'
Supply text, html, or both. Both is best — some clients will only render one.
The response carries message_id and thread_id. The message also lands in the
inbox labelled sent, so a thread reads as a conversation rather than only the
half that came back.
Four things refuse a send
In the order you will meet them:
- The organization is unverified —
403 organization_unverified. Confirm the owner email. See limits. - Quota exceeded —
429with an exactRetry-After. - The recipient is suppressed — they hard-bounced or complained. See suppression.
- The key lacks
message_send—403 insufficient_permissions.
Replying
Reply rather than send, so the conversation threads in the recipient's client too:
curl -X POST "https://api.carlyemail.com/v0/inboxes/$INBOX_ID/messages/$MESSAGE_ID/reply" \
-H "Authorization: Bearer $CARLYEMAIL_API_KEY" \
-H 'content-type: application/json' \
-d '{"text": "Thanks — looking into it now.", "reply_all": false}'
Recipients are derived from the message being replied to, so to is optional.
The reply goes to whoever sent the message — or, when the message is one this
inbox sent, on to its original recipients. reply_all: true adds everyone else
on the thread as Cc.
The inbox itself is always excluded, so an auto-replying agent cannot answer
itself in a loop. That leaves one case with nothing to derive: a message whose
only participant is this inbox, which is what a "mail yourself" test produces.
Replying to one of those returns no_reply_recipient — pass to to say where
the reply should actually go.
Naming to overrides the derived recipients entirely, which is how you redirect
an answer somewhere else. Naming cc replaces the derived Cc rather than
adding to it, so a reply_all can be narrowed as well as widened.
Scheduling
Create a draft with a future send_at timestamp to send later. The scheduled
worker runs once a minute, applies the same verification, suppression, quota,
attachment and threading rules as an immediate send, and leaves a failed draft
visible rather than silently dropping it. See Drafts for the
complete example and cancellation behavior.