curl --request POST \
--url https://core-api.getaptly.com/api/email/create-draft \
--header 'Content-Type: application/json' \
--header 'x-token: <api-key>' \
--data '
{
"userId": "user_abc",
"channelId": "channel_abc",
"to": [
{
"value": "[email protected]",
"label": "Jane Tenant"
}
],
"subject": "Your lease renewal",
"body": "Please review the attached renewal terms.",
"aptletInstanceId": "card_abc"
}
'import requests
url = "https://core-api.getaptly.com/api/email/create-draft"
payload = {
"userId": "user_abc",
"channelId": "channel_abc",
"to": [
{
"value": "[email protected]",
"label": "Jane Tenant"
}
],
"subject": "Your lease renewal",
"body": "Please review the attached renewal terms.",
"aptletInstanceId": "card_abc"
}
headers = {
"x-token": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-token': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
userId: 'user_abc',
channelId: 'channel_abc',
to: [{value: '[email protected]', label: 'Jane Tenant'}],
subject: 'Your lease renewal',
body: JSON.stringify('Please review the attached renewal terms.'),
aptletInstanceId: 'card_abc'
})
};
fetch('https://core-api.getaptly.com/api/email/create-draft', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://core-api.getaptly.com/api/email/create-draft",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'userId' => 'user_abc',
'channelId' => 'channel_abc',
'to' => [
[
'value' => '[email protected]',
'label' => 'Jane Tenant'
]
],
'subject' => 'Your lease renewal',
'body' => 'Please review the attached renewal terms.',
'aptletInstanceId' => 'card_abc'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-token: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://core-api.getaptly.com/api/email/create-draft"
payload := strings.NewReader("{\n \"userId\": \"user_abc\",\n \"channelId\": \"channel_abc\",\n \"to\": [\n {\n \"value\": \"[email protected]\",\n \"label\": \"Jane Tenant\"\n }\n ],\n \"subject\": \"Your lease renewal\",\n \"body\": \"Please review the attached renewal terms.\",\n \"aptletInstanceId\": \"card_abc\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-token", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://core-api.getaptly.com/api/email/create-draft")
.header("x-token", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"userId\": \"user_abc\",\n \"channelId\": \"channel_abc\",\n \"to\": [\n {\n \"value\": \"[email protected]\",\n \"label\": \"Jane Tenant\"\n }\n ],\n \"subject\": \"Your lease renewal\",\n \"body\": \"Please review the attached renewal terms.\",\n \"aptletInstanceId\": \"card_abc\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://core-api.getaptly.com/api/email/create-draft")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-token"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"userId\": \"user_abc\",\n \"channelId\": \"channel_abc\",\n \"to\": [\n {\n \"value\": \"[email protected]\",\n \"label\": \"Jane Tenant\"\n }\n ],\n \"subject\": \"Your lease renewal\",\n \"body\": \"Please review the attached renewal terms.\",\n \"aptletInstanceId\": \"card_abc\"\n}"
response = http.request(request)
puts response.read_body{
"streamId": "<string>",
"draftUuid": "<string>"
}{
"error": "<string>",
"message": "<string>"
}Create an email draft
Creates a new outbound email discussion (stream) with a single draft entry, scoped to
your company. Returns the streamId and draftUuid needed to send via
POST /api/email/send.
Recipient fields (to, cc, bcc) accept an array of { value, label? } objects
where value is the email address.
Adding attachments
Attachments must be uploaded to storage before you reference them here — you cannot post file bytes to this endpoint. Upload each file with the three-step direct upload flow, then attach it:
- Get an upload URL —
POST /api/files/upload-urlwithattachEntityType: "channel",attachEntityIdset to the samechannelIdyou’re emailing from, and the file’sname,extension,size, andcontentType. It returns{ fileId, url, fields }. - Upload to S3 — send a
multipart/form-dataPOSTtourl, appending every entry fromfieldsfirst and the file bytes last as afilefield. S3 returns204. - Mark complete —
POST /api/files/upload-completewith thefileIdfrom step 1.
Then, on this request:
- Regular attachment — add the
fileId(s) toattachmentIds. - Inline image — embed an
<img>in the HTMLbodywhosesrcis the file’s download URL (.../cdn/storage/AptlyFiles/<fileId>/original/..., returned by step 3). It is auto-detected and registered; do not also list it inattachmentIds.
Repeat steps 1–3 per file. Each fileId must reference a finished upload or the
request is rejected.
curl --request POST \
--url https://core-api.getaptly.com/api/email/create-draft \
--header 'Content-Type: application/json' \
--header 'x-token: <api-key>' \
--data '
{
"userId": "user_abc",
"channelId": "channel_abc",
"to": [
{
"value": "[email protected]",
"label": "Jane Tenant"
}
],
"subject": "Your lease renewal",
"body": "Please review the attached renewal terms.",
"aptletInstanceId": "card_abc"
}
'import requests
url = "https://core-api.getaptly.com/api/email/create-draft"
payload = {
"userId": "user_abc",
"channelId": "channel_abc",
"to": [
{
"value": "[email protected]",
"label": "Jane Tenant"
}
],
"subject": "Your lease renewal",
"body": "Please review the attached renewal terms.",
"aptletInstanceId": "card_abc"
}
headers = {
"x-token": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-token': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
userId: 'user_abc',
channelId: 'channel_abc',
to: [{value: '[email protected]', label: 'Jane Tenant'}],
subject: 'Your lease renewal',
body: JSON.stringify('Please review the attached renewal terms.'),
aptletInstanceId: 'card_abc'
})
};
fetch('https://core-api.getaptly.com/api/email/create-draft', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://core-api.getaptly.com/api/email/create-draft",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'userId' => 'user_abc',
'channelId' => 'channel_abc',
'to' => [
[
'value' => '[email protected]',
'label' => 'Jane Tenant'
]
],
'subject' => 'Your lease renewal',
'body' => 'Please review the attached renewal terms.',
'aptletInstanceId' => 'card_abc'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-token: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://core-api.getaptly.com/api/email/create-draft"
payload := strings.NewReader("{\n \"userId\": \"user_abc\",\n \"channelId\": \"channel_abc\",\n \"to\": [\n {\n \"value\": \"[email protected]\",\n \"label\": \"Jane Tenant\"\n }\n ],\n \"subject\": \"Your lease renewal\",\n \"body\": \"Please review the attached renewal terms.\",\n \"aptletInstanceId\": \"card_abc\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-token", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://core-api.getaptly.com/api/email/create-draft")
.header("x-token", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"userId\": \"user_abc\",\n \"channelId\": \"channel_abc\",\n \"to\": [\n {\n \"value\": \"[email protected]\",\n \"label\": \"Jane Tenant\"\n }\n ],\n \"subject\": \"Your lease renewal\",\n \"body\": \"Please review the attached renewal terms.\",\n \"aptletInstanceId\": \"card_abc\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://core-api.getaptly.com/api/email/create-draft")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-token"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"userId\": \"user_abc\",\n \"channelId\": \"channel_abc\",\n \"to\": [\n {\n \"value\": \"[email protected]\",\n \"label\": \"Jane Tenant\"\n }\n ],\n \"subject\": \"Your lease renewal\",\n \"body\": \"Please review the attached renewal terms.\",\n \"aptletInstanceId\": \"card_abc\"\n}"
response = http.request(request)
puts response.read_body{
"streamId": "<string>",
"draftUuid": "<string>"
}{
"error": "<string>",
"message": "<string>"
}Authorizations
Body
User to record as the draft creator/sender.
channel.meta.id of the outbound email channel.
Company ID. Required only when authenticating with a partner token; resolved automatically for API key and delegate token auth.
When set, append the draft as a reply to this existing thread (stream ID) instead of starting a new discussion. The thread must belong to the same company and channel. The thread's subject is kept and the draft is threaded onto the thread's last message.
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Email body (plain text or HTML). To embed an uploaded image inline, add
an <img> whose src is the file's download URL from
POST /api/files/upload-complete
(.../cdn/storage/AptlyFiles/<fileId>/original/...). Inline images are
auto-detected, tagged with data-inline-image-id, and registered on the
draft — do not also list them in attachmentIds.
File ids to include as regular attachments. Upload each file first
(POST /api/files/upload-url with attachEntityType channel and the same
channelId → upload to S3 → POST /api/files/upload-complete), then pass the
resulting fileIds here. Each must reference a finished upload.
Card to link the outbound to. When set, sending the draft logs the email as an activity on the card and tags the discussion with it.