curl --request POST \
--url https://app.lettr.com/api/emails/scheduled \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"from": "sender@example.com",
"from_name": "Sender Name",
"to": [
"recipient@example.com"
],
"subject": "Scheduled Newsletter",
"html": "<h1>Hello</h1><p>This email was scheduled!</p>",
"scheduled_at": "2024-01-16T10:00:00Z",
"options": {
"click_tracking": true,
"open_tracking": true
}
}
'import requests
url = "https://app.lettr.com/api/emails/scheduled"
payload = {
"from": "sender@example.com",
"from_name": "Sender Name",
"to": ["recipient@example.com"],
"subject": "Scheduled Newsletter",
"html": "<h1>Hello</h1><p>This email was scheduled!</p>",
"scheduled_at": "2024-01-16T10:00:00Z",
"options": {
"click_tracking": True,
"open_tracking": True
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
from: 'sender@example.com',
from_name: 'Sender Name',
to: ['recipient@example.com'],
subject: 'Scheduled Newsletter',
html: '<h1>Hello</h1><p>This email was scheduled!</p>',
scheduled_at: '2024-01-16T10:00:00Z',
options: {click_tracking: true, open_tracking: true}
})
};
fetch('https://app.lettr.com/api/emails/scheduled', 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://app.lettr.com/api/emails/scheduled",
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([
'from' => 'sender@example.com',
'from_name' => 'Sender Name',
'to' => [
'recipient@example.com'
],
'subject' => 'Scheduled Newsletter',
'html' => '<h1>Hello</h1><p>This email was scheduled!</p>',
'scheduled_at' => '2024-01-16T10:00:00Z',
'options' => [
'click_tracking' => true,
'open_tracking' => true
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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://app.lettr.com/api/emails/scheduled"
payload := strings.NewReader("{\n \"from\": \"sender@example.com\",\n \"from_name\": \"Sender Name\",\n \"to\": [\n \"recipient@example.com\"\n ],\n \"subject\": \"Scheduled Newsletter\",\n \"html\": \"<h1>Hello</h1><p>This email was scheduled!</p>\",\n \"scheduled_at\": \"2024-01-16T10:00:00Z\",\n \"options\": {\n \"click_tracking\": true,\n \"open_tracking\": true\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://app.lettr.com/api/emails/scheduled")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"from\": \"sender@example.com\",\n \"from_name\": \"Sender Name\",\n \"to\": [\n \"recipient@example.com\"\n ],\n \"subject\": \"Scheduled Newsletter\",\n \"html\": \"<h1>Hello</h1><p>This email was scheduled!</p>\",\n \"scheduled_at\": \"2024-01-16T10:00:00Z\",\n \"options\": {\n \"click_tracking\": true,\n \"open_tracking\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.lettr.com/api/emails/scheduled")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"from\": \"sender@example.com\",\n \"from_name\": \"Sender Name\",\n \"to\": [\n \"recipient@example.com\"\n ],\n \"subject\": \"Scheduled Newsletter\",\n \"html\": \"<h1>Hello</h1><p>This email was scheduled!</p>\",\n \"scheduled_at\": \"2024-01-16T10:00:00Z\",\n \"options\": {\n \"click_tracking\": true,\n \"open_tracking\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"message": "Email scheduled for delivery.",
"data": {
"request_id": "sch_01JQZ3N2K8XW9V6M4TBRC7YHDE",
"transmission_id": null,
"state": "scheduled",
"scheduled_at": "2024-01-16T10:00:00Z",
"from": "sender@example.com",
"from_name": "Sender Name",
"subject": "Scheduled Newsletter",
"recipients": [
"recipient@example.com"
],
"num_recipients": 1,
"accepted": 1,
"rejected": 0,
"tag": null,
"failure_reason": null,
"events": []
}
}Schedule Email
Schedule a transactional email for future delivery. Accepts the same payload as POST /emails with an additional required scheduled_at field. The email must be scheduled at least 5 minutes in the future and within 30 days.
Breaking change. Scheduled emails are no longer parked inside SparkPost with options.start_time — SparkPost retired its per-transmission GET and DELETE endpoints, so Lettr now holds the email in its own store and injects it into SparkPost only when it is due. request_id is therefore Lettr’s own id, prefixed sch_ (for example sch_01JQZ3N2K8XW9V6M4TBRC7YHDE), and it is the id you pass to GET and DELETE /emails/scheduled/{transmissionId}. SparkPost’s transmission id is now the separate transmission_id field, which is null until the email is actually injected; that is the value that appears as transmission_id on your webhook events, so use it to correlate webhooks. state values are now Lettr’s own (scheduled, sending, sent, cancelled, failed) rather than SparkPost’s pass-through states.
Idempotency-Key is not honoured here and is ignored if sent — it applies to POST /emails only. A scheduled email is created once and then listed, read or cancelled by the request_id returned here, so use that to check whether a retry is needed.
curl --request POST \
--url https://app.lettr.com/api/emails/scheduled \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"from": "sender@example.com",
"from_name": "Sender Name",
"to": [
"recipient@example.com"
],
"subject": "Scheduled Newsletter",
"html": "<h1>Hello</h1><p>This email was scheduled!</p>",
"scheduled_at": "2024-01-16T10:00:00Z",
"options": {
"click_tracking": true,
"open_tracking": true
}
}
'import requests
url = "https://app.lettr.com/api/emails/scheduled"
payload = {
"from": "sender@example.com",
"from_name": "Sender Name",
"to": ["recipient@example.com"],
"subject": "Scheduled Newsletter",
"html": "<h1>Hello</h1><p>This email was scheduled!</p>",
"scheduled_at": "2024-01-16T10:00:00Z",
"options": {
"click_tracking": True,
"open_tracking": True
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
from: 'sender@example.com',
from_name: 'Sender Name',
to: ['recipient@example.com'],
subject: 'Scheduled Newsletter',
html: '<h1>Hello</h1><p>This email was scheduled!</p>',
scheduled_at: '2024-01-16T10:00:00Z',
options: {click_tracking: true, open_tracking: true}
})
};
fetch('https://app.lettr.com/api/emails/scheduled', 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://app.lettr.com/api/emails/scheduled",
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([
'from' => 'sender@example.com',
'from_name' => 'Sender Name',
'to' => [
'recipient@example.com'
],
'subject' => 'Scheduled Newsletter',
'html' => '<h1>Hello</h1><p>This email was scheduled!</p>',
'scheduled_at' => '2024-01-16T10:00:00Z',
'options' => [
'click_tracking' => true,
'open_tracking' => true
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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://app.lettr.com/api/emails/scheduled"
payload := strings.NewReader("{\n \"from\": \"sender@example.com\",\n \"from_name\": \"Sender Name\",\n \"to\": [\n \"recipient@example.com\"\n ],\n \"subject\": \"Scheduled Newsletter\",\n \"html\": \"<h1>Hello</h1><p>This email was scheduled!</p>\",\n \"scheduled_at\": \"2024-01-16T10:00:00Z\",\n \"options\": {\n \"click_tracking\": true,\n \"open_tracking\": true\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://app.lettr.com/api/emails/scheduled")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"from\": \"sender@example.com\",\n \"from_name\": \"Sender Name\",\n \"to\": [\n \"recipient@example.com\"\n ],\n \"subject\": \"Scheduled Newsletter\",\n \"html\": \"<h1>Hello</h1><p>This email was scheduled!</p>\",\n \"scheduled_at\": \"2024-01-16T10:00:00Z\",\n \"options\": {\n \"click_tracking\": true,\n \"open_tracking\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.lettr.com/api/emails/scheduled")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"from\": \"sender@example.com\",\n \"from_name\": \"Sender Name\",\n \"to\": [\n \"recipient@example.com\"\n ],\n \"subject\": \"Scheduled Newsletter\",\n \"html\": \"<h1>Hello</h1><p>This email was scheduled!</p>\",\n \"scheduled_at\": \"2024-01-16T10:00:00Z\",\n \"options\": {\n \"click_tracking\": true,\n \"open_tracking\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"message": "Email scheduled for delivery.",
"data": {
"request_id": "sch_01JQZ3N2K8XW9V6M4TBRC7YHDE",
"transmission_id": null,
"state": "scheduled",
"scheduled_at": "2024-01-16T10:00:00Z",
"from": "sender@example.com",
"from_name": "Sender Name",
"subject": "Scheduled Newsletter",
"recipients": [
"recipient@example.com"
],
"num_recipients": 1,
"accepted": 1,
"rejected": 0,
"tag": null,
"failure_reason": null,
"events": []
}
}POST /emails, including custom headers, with an additional required scheduled_at field.
The response returns a Lettr ID that starts with sch_. Use it to get or cancel the email. transmission_id stays null until the email is actually sent, so don’t read it from the scheduling response.
A scheduled email counts against your quota on the day it is sent, not the day you schedule it. The Idempotency-Key header is ignored on this endpoint.Authorizations
API key for authentication
Body
Schedule email request. Extends the standard send email request with a required scheduled_at field.
Sender email address
255"sender@example.com"
Recipient email addresses
1 - 50 elements255["recipient@example.com"]
The UTC date/time when the email should be sent. Must be at least 5 minutes in the future and within 30 days.
"2024-01-16T10:00:00Z"
Sender display name
255"Sender Name"
Email subject line. Required when not using a template. When using template_slug, defaults to the template's subject (or template name as fallback). If provided alongside a template, overrides the template subject.
998"Welcome to Lettr"
Carbon copy recipient email addresses
255["cc@example.com"]
Blind carbon copy recipient email addresses
255["bcc@example.com"]
Reply-To email address
255"reply@example.com"
Reply-To display name
255"Reply Name"
HTML content of the email. At least one of html or text is required, but providing both is recommended for best compatibility across email clients.
"<h1>Hello</h1><p>Welcome!</p>"
Plain text content of the email. At least one of html or text is required, but providing both is recommended for best compatibility across email clients.
"Hello\n\nWelcome!"
AMP HTML content for supported email clients
Project ID containing the template. If not provided when using template_slug, the team's default project will be used.
123
Template slug to use for email content. When provided, the template's HTML will be used instead of the html field.
255"welcome-email"
Specific template version to use. If not provided, the active version will be used.
x >= 11
Tag for tracking and analytics. Automatically set from template_slug if not provided.
64"welcome-series-2024"
Custom metadata attached to the email for tracking purposes
Show child attributes
Show child attributes
{
"user_id": "12345",
"campaign": "onboarding"
}
Custom email headers. Up to 10 headers allowed. Standard envelope headers (From, To, Subject, etc.), SparkPost internal headers (X-MSYS-API), and List-Unsubscribe and List-Unsubscribe-Post headers are managed by the system and cannot be set manually.
Show child attributes
Show child attributes
{
"X-Mailer": "MyApp/1.0",
"X-Custom-ID": "abc-123"
}
Variables for template substitution in email content
Show child attributes
Show child attributes
{
"first_name": "John",
"company": "Acme Inc"
}
Email delivery options
Show child attributes
Show child attributes
File attachments. When provided, each attachment must include name, type, and data.
Show child attributes
Show child attributes
Response
Email scheduled for delivery. Free tier teams receive quota headers indicating their usage and limits.
"Email scheduled for delivery."
A scheduled email held by Lettr. This is the data object of POST /emails/scheduled, GET /emails/scheduled/{transmissionId} and DELETE /emails/scheduled/{transmissionId}, and each entry of scheduled_emails in GET /emails/scheduled.
Breaking change. Scheduled emails are no longer parked inside SparkPost with options.start_time — SparkPost retired its per-transmission GET and DELETE endpoints. Lettr now holds the email in its own store and injects it into SparkPost when it is due, so request_id is Lettr's own sch_... id, transmission_id is a separate field that stays null until injection, and state values are Lettr's own rather than SparkPost's pass-through states.
Show child attributes
Show child attributes
Was this page helpful?