agentlyleads docs
Emails API

POST /api/v1/emails

Compose an email to one contact. The email is saved as a DRAFT and grouped under a campaign. Requires the write:emails scope (or *).

Draft-first

This call never sends. Review the draft at its reviewUrl, or release it with POST /api/v1/emails/:id/send.

Request

POST /api/v1/emails HTTP/1.1
Host: agentlyleads.com
Authorization: Bearer alk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

{
  "contactExternalId": "hs_contact_123",
  "subject": "Pickleball sets, 15,830 sets, in Los Angeles",
  "bodyHtml": "<p>{{firstName}},</p><p>Branded pickleball sets already in the US...</p>",
  "campaign": "DEAL-1995 Pickleball outreach",
  "from": "kevin@example.com",
  "contactContext": "Pitched pickleball 2026-09-08 at $5.00. Next: his volume."
}

Body

FieldDescription
contactId or contactExternalIdThe recipient. One is required. The To address is always the contact's own email on file, never a caller-supplied address.
subjectRequired. {{firstName}} and {{company}} are replaced at send time.
bodyHtmlRequired. Email-client-safe HTML: inline styles only, no <style> blocks or classes. Same tokens as subject.
campaignA short title to group related emails under. Reused if it exists. Omit to file under the standing "Direct Outreach" campaign.
fromA sending identity from GET /api/v1/emails/identities. Any other address is rejected with 400.
offeringIdLink the email to an offering so it shows in that offering's communication view.
contactContextA compact rolling summary saved on the contact (aiContext, 4,000 characters max). Merge the existing context with what this email did and the next step.
attachmentsUp to 10. Each has fileName and exactly one of contentBase64 (10 MB decoded max) or a public https url the server downloads.
asUserEmailAct as this workspace member. Defaults to the workspace owner.

Response — 201

{
  "email": {
    "id": "cmtsv6ts404hzii01dnmu4k5t",
    "status": "DRAFT",
    "direction": "OUTBOUND",
    "subject": "Pickleball sets, 15,830 sets, in Los Angeles",
    "toEmail": "jane@buyer.test",
    "fromEmail": "kevin@example.com",
    "contactId": "cmr8dlczc00xkic01prvioybz",
    "campaignId": "cmtsv6tr704hxii01xyz",
    "campaign": { "id": "cmtsv6tr704hxii01xyz", "name": "DEAL-1995 Pickleball outreach" },
    "attachmentCount": 0,
    "reviewUrl": "https://agentlyleads.com/emails/cmtsv6ts404hzii01dnmu4k5t",
    "createdAt": "2026-09-08T16:16:05.256Z"
  },
  "contact": { "id": "cmr8dlczc00xkic01prvioybz", "name": "Jane Buyer", "email": "jane@buyer.test" },
  "contextSaved": true,
  "otherPendingDrafts": []
}

otherPendingDrafts lists up to five other drafts already waiting on the same contact, so a superseded draft is not left behind to be sent by mistake. Delete one with DELETE /api/v1/emails/:id.

Reply in a thread

To continue an existing conversation instead of starting a new one, use POST /api/v1/emails/:id/reply with the id of the email being answered. The reply is drafted to that email's contact, with a Re: subject, threaded onto the original.

POST /api/v1/emails/cmtsy2o2v05peii01g2ebysb0/reply HTTP/1.1
Authorization: Bearer alk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

{ "bodyHtml": "<p>Fair enough. What does the take-all need to be a pack?</p>" }

Returns 201 with the same email object plus inReplyTo.

On this page