Outreach Emails
Draft personalized emails, review them, and send from your connected inbox.
#Use case
Draft personalized emails, review them, and send from your connected inbox.
#Endpoint
POST https://api-public.clodo.ai/api/public/v1/emails/Send your API key in the x-api-key header. See Authentication.
This endpoint returns
202 Accepted. Poll the resource to follow its progress. Outreach does not require a webhook signing secret.
#Pricing
See Credits & Pricing for credit costs and charging behavior.
#Errors
For error responses and retry guidance, see Handling Errors.
Create AI-drafted outreach emails, review them, and send them from your own connected inbox — the full draft → review → approve → send loop over the API. 2 credits per drafted email, charged when the draft succeeds. Reviewing, editing, approving, cancelling, and reading are free.
Emails send from a mailbox you've connected in the clodo web app (see Sending Accounts), and every email you create here also appears on your Emails page in the app — same review queue, same thread history, same open and reply tracking.
This is not a raw email-sending API: you supply a contact and instructions, clodo drafts the copy. Every recipient is saved as a lead in your workspace.
#Lifecycle
POST /emails/ -> 202 status: drafting
GET /emails/{id}/ -> poll until status: pending_review
PATCH /emails/{id}/ -> optional draft edits
POST /emails/{id}/approve/ -> status: queued (then sending -> sent)| Status | Meaning |
|---|---|
drafting |
The draft is being written (typically 10–60s). |
pending_review |
Draft ready — waiting for your approval. Expires after 7 days (see review_expires_at). |
queued |
Approved; waiting for its send slot. |
sending / sent |
In flight / delivered to your provider. |
failed |
Drafting or sending failed (see error). Failed drafts are not charged. |
cancelled |
Cancelled by you, or expired unapproved. |
Pass "review": "none" at create to skip the review gate — the draft goes straight to queued with an automatically chosen send time. The default ("required") is recommended: nothing sends without an explicit approve.
#Create — POST /emails/
| Field | Type | Notes |
|---|---|---|
to.email |
string | Required. Recipient address — becomes/updates a lead in your workspace. |
to.first_name |
string | Required. |
to.last_name |
string | Optional. |
to.company_name |
string | Optional but recommended — improves the draft. |
to.professional_url |
string | Optional LinkedIn URL. |
to.context |
string | Optional free-text grounding, ≤1000 chars (e.g. "raised Series B last month"). |
instructions |
string | Required, ≤2000 chars. What the email should say/do. |
from_account |
string | Optional ea_... id (see Sending Accounts). Defaults to your least-loaded connected account. |
reply_to |
string | Optional Reply-To address. Must be one of your connected email accounts; anything else is a 400. |
cc |
string[] | Optional CC recipients, max 3. Visible to everyone on the thread. A reply from a CC'd address is not treated as the lead replying. |
bcc |
string[] | Optional BCC recipients, max 3 (e.g. a CRM logging address). Never visible to the other recipients. |
review |
string | "required" (default) or "none". |
attachments |
array | Optional files. Up to 5 files and 1 MiB decoded bytes combined. See Attachments below. |
curl -X POST https://api-public.clodo.ai/api/public/v1/emails/ \
-H "x-api-key: ck_live_..." \
-H "Content-Type: application/json" \
-d '{
"to": {
"email": "jackie@acme.com",
"first_name": "Jackie",
"company_name": "Acme",
"context": "spoke at SaaStr about PLG onboarding"
},
"instructions": "Casual note asking if she is open to hearing about our founding PM role."
}'Returns 202:
{ "id": "em_1f0c9a...", "status": "drafting", "created_at": "2026-07-13T00:00:00Z" }409 conflict means you have no usable sending account — connect one in the clodo web app first.
#Read — GET /emails/{id}/
Poll this while drafting; afterwards it's the live state of the email (opens, replies, schedule).
{
"id": "em_1f0c9a...",
"status": "pending_review",
"to": { "email": "jackie@acme.com", "name": "Jackie" },
"from_email": "you@yourcompany.com",
"subject": "Founding PM at Acme?",
"body_plain": "Hi Jackie, ...",
"body_html": "<p>Hi Jackie, ...</p>",
"instructions": "Casual note asking...",
"attachments": [],
"created_at": "2026-07-13T00:00:00Z",
"scheduled_for": null,
"sent_at": null,
"opened_at": null,
"open_count": 0,
"replied_at": null,
"review_expires_at": "2026-07-20T00:00:10Z",
"error": null
}#List — GET /emails/
Paginated, newest first. Filters: ?status=<any wire status>, ?since=<ISO 8601>, ?page=, ?page_size= (default 50, max 200). Returns {count, page, page_size, total_pages, results}. Only emails created through the API appear here.
curl "https://api-public.clodo.ai/api/public/v1/emails/?status=replied&since=2026-07-01T00:00:00Z" \
-H "x-api-key: ck_live_..."#Edit — PATCH /emails/{id}/
Allowed while pending_review or queued (approved but not yet sent — including scheduled follow-up steps of a running sequence). Send subject, attachments, and/or one of body_plain / body_html (not both body formats). HTML is sanitized server-side. Editing while pending_review extends the 7-day review window. Once a message is sending or sent the edit returns 409.
curl -X PATCH https://api-public.clodo.ai/api/public/v1/emails/em_1f0c9a.../ \
-H "x-api-key: ck_live_..." \
-H "Content-Type: application/json" \
-d '{"subject": "Quick question about Acme"}'#Attachments
Attachment uploads are a private pilot and require both deployment activation and explicit access for the authenticated clodo account. Other accounts receive 400 for nonempty attachment requests, even in the same workspace or when using an allowed sender alias. Ordinary emails and clearing files remain available.
Supply files on POST /emails/ or replace them on PATCH /emails/{id}/ while the email is pending_review or queued:
{
"attachments": [
{
"filename": "notes.txt",
"content_type": "text/plain",
"content_base64": "SGVsbG8="
}
]
}Each file requires exactly filename, content_type, and content_base64. Use standard base64 with padding, without a data-URL prefix or whitespace. Filenames must be at most 180 characters and cannot contain paths or control characters. Files must be nonempty. Each file and the combined decoded contents must fit within 1 MiB; at most 5 files are allowed.
| Content type | Filename extensions | Content check |
|---|---|---|
application/pdf |
.pdf |
PDF header |
image/png |
.png |
PNG signature |
image/jpeg |
.jpg, .jpeg |
JPEG start/end markers |
text/plain |
.txt |
UTF-8 text |
text/csv |
.csv |
UTF-8 text |
These are format checks, not malware scanning. Invalid filenames, types, base64, empty files, or size limits return 400 before drafting or reserving credits. URLs, local paths, executable files, and inline images are not supported.
On PATCH, omitting attachments preserves the files; supplying a list replaces all files; [] removes them. Files can be edited while queued, until sending begins. Sending and sent emails cannot be edited. Attachments remain with the email through drafting and scheduling, including review: "none", and are sent with now, at, or best_time approval. They are not passed to the AI draft writer; mention any desired attachment references in instructions.
Reads return metadata only:
{"attachments": [{"filename": "notes.txt", "content_type": "text/plain", "size_bytes": 5}]}File contents are stored with the private email record and retained with that record. They are not returned by read endpoints or exposed through download URLs. Cancelling stops delivery but does not delete the record or its files. Follow-up emails do not automatically inherit attachments.
#Approve — POST /emails/{id}/approve/
Only while pending_review. Returns the updated email.
| Body | Behavior |
|---|---|
{"schedule": "best_time"} |
Default. clodo picks the optimal send time for the recipient. |
{"schedule": "now"} |
Next available send slot. |
{"schedule": "at", "send_at": "2026-07-14T16:00:00Z"} |
Fixed UTC time. |
#Cancel — POST /emails/{id}/cancel/
Allowed while drafting, pending_review, or queued. Cancelling mid-draft costs nothing; cancelling an already-drafted email does not refund the 2 credits (the drafting work happened).
#Status mapping
| Outcome | HTTP | Charged |
|---|---|---|
| Draft created | 202 |
2 credits when the draft succeeds |
| Draft failed | — (status: failed on poll) |
0 |
| Invalid input | 400 invalid_request |
0 |
| No usable sending account | 409 conflict |
0 |
| Edit/approve/cancel in wrong state | 409 conflict |
0 |
Unknown em_ id |
404 not_found |
0 |
#Notes
- Webhooks are optional for these endpoints — polling is first-class. If you never register a webhook secret, everything above still works.
- Daily send limits on your connected accounts always apply; approving more than the day's limit queues the remainder for the following days.
- Sends stop automatically when the recipient replies (visible via
replied_at).