The Nylas Messages API allows you to schedule messages to be sent in the future. This means you can draft a message in advance and schedule it to send when the time is just right, for example when you want to announce a new version of your product.
How scheduled send works
Section titled “How scheduled send works”When you make a request to the Send Message endpoint, you can define your preferred send time (in seconds using the Unix timestamp format) in the send_at field.
If the user’s provider is Google or Microsoft, you can choose to store the scheduled message in their Drafts folder on the provider. In this case, you can schedule it to send any time in the future.
You can also choose to have Nylas store the message until its send time, which must be between 2 minutes and 30 days in the future.
Before you begin
Section titled “Before you begin”Before you start scheduling messages, you need the following prerequisites:
- A Nylas application.
- A working authentication configuration. Either…
- A Google or Microsoft grant with at least the following scopes:
- Google:
gmail.send - Microsoft:
Mail.ReadWriteandMail.Send
- Google:
Schedule a message
Section titled “Schedule a message”To schedule a message to be sent in the future, make a Send Message request that includes the send_at field, and provide the time (in Unix timestamp format) when you want the message to be sent. Nylas returns a schedule_id that you can use to reference the scheduled message.
curl --request POST \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/send' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json' \ --data '{ "subject": "Reaching out with Nylas", "body": "Hey! This is testing scheduled emails.", "to": [{ "name": "Leyah Miller", "email": "[email protected]" }], "send_at": 1714478400 }'{ "request_id": "1", "grant_id": "<NYLAS_GRANT_ID>", "data": { "body": "Hey! This is testing scheduled emails.", "from": [ { } ], "subject": "Reaching out with Nylas", "to": [ { "name": "John Doe", } ], "reply_to_message_id": "", "send_at": 1714478400, "use_draft": false, "schedule_id": "8cd56334-6d95-432c-86d1-c5dab0ce98be" }}import Nylas from "nylas";
const NylasConfig = { apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",};
const nylas = new Nylas(NylasConfig);
async function scheduleSendEmail() { try { const sentMessage = await nylas.messages.send({ identifier: "<GRANT_ID>", requestBody: { to: [{ name: "Ram", email: "<EMAIL>" }], replyTo: [{ name: "Ram", email: "<EMAIL>" }], subject: "Your Subject Here", body: "Your email body here.", sendAt: 1719105508, }, });
console.log("Email scheduled:", sentMessage); } catch (error) { console.error("Error scheduling email:", error); }}
scheduleSendEmail();import sysfrom nylas import Client
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>")
grant_id = "<GRANT_ID>"email = "<EMAIL>"
message = nylas.messages.send( grant_id, request_body={ "to": [{ "name": "Ram", "email": email }], "reply_to": [{ "name": "Ram", "email": email }], "subject": "Your Subject Here", "body": "Your email body here.", "send_at": 1713824548 })
print(message)require 'nylas'
nylas = Nylas::Client.new( api_key: "<NYLAS_API_KEY>")
request_body = { subject: "Reaching out with Nylas", body: "Hey! This is testing scheduled emails.", send_at: 1714639370}
email, _ = nylas.messages.send(identifier: "<NYLAS_GRANT_ID>", request_body: request_body)puts "Message \"#{email[:subject]}\" was sent"import com.nylas.NylasClient;import com.nylas.models.*;import java.util.ArrayList;import java.util.List;
public class SendScheduledEmails { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError {
NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
List<EmailName> emailNames = new ArrayList<>();
SendMessageRequest requestBody = new SendMessageRequest.Builder(emailNames). subject("Reaching out with Nylas"). body("Hey! This is testing scheduled emails."). sendAt(1714640910). build();
Response<Message> email = nylas.messages().send("<NYLAS_GRANT_ID>", requestBody); System.out.println(email.getData()); }}import com.nylas.NylasClientimport com.nylas.models.*
fun main(args: Array<String>) {
val nylas: NylasClient = NylasClient( apiKey = "<NYLAS_API_KEY>" )
val requestBody : SendMessageRequest = SendMessageRequest.Builder(emailNames). subject("Reaching out with Nylas"). body("Hey! This is testing scheduled emails."). sendAt(1714640910L). build() val email = nylas.messages().send("<NYLAS_GRANT_ID>",requestBody) print(email.data)}nylas email send \ --subject "Reaching out with Nylas" \ --body "Hey! This is testing scheduled emails." \ --schedule "tomorrow 9am"The --schedule flag in the Nylas CLI accepts relative times ("in 2 hours", "tomorrow 9am") and absolute timestamps. See nylas email send for supported formats.
If you’re storing the message on Nylas, you can schedule it to be sent between 2 minutes and 30 days in the future. If you’re storing it on the provider, you can schedule it to be sent any time in the future.
To store the message as a draft on the provider, set use_draft to true.
Use a custom tracking hostname for a scheduled message
Section titled “Use a custom tracking hostname for a scheduled message”Add tracking_options.domain_name to a scheduled send when you want rewritten links and tracking images to use an active organization-owned hostname. Register the hostname first by following the custom tracking hostname setup.
Nylas validates the hostname when it creates the schedule, so malformed, unowned, inactive, deleted, or blocked values fail synchronously with a generic 400 response. Nylas checks the normalized hostname again immediately before delivery because its ownership, certificate, or blocklist status can change while the message waits.
The execution path depends on the scheduled send family:
| Scheduled send family | Tracking and revalidation behavior |
|---|---|
| Grant-based message, including a provider-stored scheduled draft | Nylas injects tracking when it creates the schedule. Before the provider call, Nylas checks the custom hostname again without rewriting the tracking URLs a second time. |
| Pure Transactional Send | Nylas injects tracking when it creates the schedule. Immediately before the SES call, Nylas checks the custom hostname again without rewriting the tracking URLs a second time. The route domain remains the sender domain, and the body field remains the tracking hostname. |
| Agent Account message or draft | Nylas validates and stores domain_name when it creates the schedule. Inbox preserves the field during replay, then the normal send path checks the hostname again and injects tracking at execution. |
Scheduled drafts follow the behavior for their provider family. If the hostname becomes inactive, is deleted, changes ownership, or is blocked before execution, the send fails through the existing asynchronous failure path. Nylas doesn’t fall back to its regional tracking hostname. Use the message.send_failed notification to monitor delivery failures.
Schedule a draft
Section titled “Schedule a draft”If you want to schedule a message to be sent later, but you’re not quite set on what you want the content to be yet, you can schedule a draft. To do this, make a Send Message request that includes the send_at field, and specify "use_draft": true.
curl --request POST \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/send' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json' \ --data '{ "subject": "Reaching out with Nylas", "body": "Hey! This is testing scheduled drafts.", "to": [{ "name": "Leyah Miller", "email": "[email protected]" }], "send_at": 1714478400, "use_draft": true }'import Nylas from "nylas";
const NylasConfig = { apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",};
const nylas = new Nylas(NylasConfig);
async function scheduleSendEmail() { try { const sentMessage = await nylas.messages.send({ identifier: "<GRANT_ID>", requestBody: { to: [{ name: "Ram", email: "<EMAIL>" }], replyTo: [{ name: "Ram", email: "<EMAIL>" }], subject: "Your Subject Here", body: "Your email body here.", sendAt: 1719105508, useDraft: true, }, });
console.log("Email scheduled:", sentMessage); } catch (error) { console.error("Error scheduling email:", error); }}
scheduleSendEmail();import sysfrom nylas import Client
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>")
grant_id = "<GRANT_ID>"email = "<EMAIL>"
message = nylas.messages.send( grant_id, request_body={ "to": [{ "name": "Ram", "email": email }], "reply_to": [{ "name": "Ram", "email": email }], "subject": "Your Subject Here", "body": "Your email body here.", "send_at": 1713824548, "use_draft": True })
print(message)require 'nylas'
nylas = Nylas::Client.new( api_key: "<NYLAS_API_KEY>")
request_body = { subject: "Reaching out with Nylas", body: "Hey! This is testing scheduled emails.", send_at: 1714639370, use_draft: true}
email, _ = nylas.messages.send(identifier: "<NYLAS_GRANT_ID>", request_body: request_body)puts "Message \"#{email[:subject]}\" was sent"import com.nylas.NylasClient;import com.nylas.models.*;import java.util.ArrayList;import java.util.List;
public class SendScheduledEmails { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError {
NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
List<EmailName> emailNames = new ArrayList<>();
SendMessageRequest requestBody = new SendMessageRequest.Builder(emailNames). subject("Reaching out with Nylas"). body("Hey! This is testing scheduled emails."). sendAt(1714640910). useDraft(true). build();
Response<Message> email = nylas.messages().send("<NYLAS_GRANT_ID>", requestBody); System.out.println(email.getData()); }}import com.nylas.NylasClientimport com.nylas.models.*
fun main(args: Array<String>) {
val nylas: NylasClient = NylasClient( apiKey = "<NYLAS_API_KEY>" )
val requestBody : SendMessageRequest = SendMessageRequest.Builder(emailNames). subject("Reaching out with Nylas"). body("Hey! This is testing scheduled emails."). sendAt(1714640910L). useDraft(true). build() val email = nylas.messages().send("<NYLAS_GRANT_ID>",requestBody) print(email.data)}Nylas saves the message in the user’s Drafts folder until the defined send_at time.
You can edit a scheduled draft until 10 seconds before the defined send_at time. To do this, make an Update Draft request that includes the draft_id and the fields you want to modify.
If you want to update a draft’s scheduled send time, you must delete the schedule and create a new one.
Read scheduled messages
Section titled “Read scheduled messages”You can make a Get Scheduled Messages request or use the Nylas SDKs to get information about all of your scheduled messages. Nylas returns a list of schedule instructions, including their schedule IDs and information about their status.
curl --request GET \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/schedules' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'[ { "schedule_id": "8cd56334-6d95-432c-86d1-c5dab0ce98be", "status": { "code": "pending", "description": "schedule send awaiting send at time" } }, { "schedule_id": "rb856334-6d95-432c-86d1-c5dab0ce98be", "status": { "code": "success", "description": "schedule send succeeded" }, "close_time": 1690579819 }]import Nylas from "nylas";
const nylas = new Nylas({ apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",});
async function fetchMessageSchedules() { try { const identifier = "<NYLAS_GRANT_ID>"; const messageSchedules = await nylas.messages.listScheduledMessages({ identifier, });
console.log("Message Schedules:", messageSchedules); } catch (error) { console.error("Error fetching message schedules:", error); }}
fetchMessageSchedules();import sysfrom nylas import Client
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>")
grant_id = "<NYLAS_GRANT_ID>"
messages = nylas.messages.list_scheduled_messages( grant_id)
print(messages)require 'nylas'
# Initialize Nylas clientnylas = Nylas::Client.new( api_key: "<NYLAS_API_KEY>")
messages, _ = nylas.messages.list_scheduled_messages(identifier: "<NYLAS_GRANT_ID>")
messages.each {|message| puts message}import com.nylas.NylasClient;import com.nylas.models.*;
public class ReturnMessage { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
ListResponse<ScheduledMessage> message = nylas.messages().listScheduledMessages("<NYLAS_GRANT_ID>");
System.out.println(message.getData()); }}import com.nylas.NylasClient
fun main(args: Array<String>) { val nylas: NylasClient = NylasClient( apiKey = "<NYLAS_API_KEY>" )
val messages = nylas.messages().listScheduledMessages("<NYLAS_GRANT_ID>").data
print(messages)}nylas email scheduled listSee nylas email scheduled list for full options. If you see a schedule instruction that you’re interested in, you can pass its schedule_id in a Get Scheduled Message request or using the Nylas SDKs to get information about it.
curl --request GET \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/schedules/<SCHEDULE_ID>' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'{ "schedule_id": "8cd56334-6d95-432c-86d1-c5dab0ce98be", "status": { "code": "pending", "description": "schedule send awaiting send at time" }}import Nylas from "nylas";
const NylasConfig = { apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",};
const nylas = new Nylas(NylasConfig);
async function fetchScheduledMessageById() { try { const events = await nylas.messages.findScheduledMessage({ identifier: "<NYLAS_GRANT_ID>", scheduleId: "<SCHEDULE_ID>", });
console.log("Events:", events); } catch (error) { console.error("Error fetching calendars:", error); }}
fetchScheduledMessageById();import sysfrom nylas import Client
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>")
grant_id = "<NYLAS_GRANT_ID>"schedule_id = "<SCHEDULE_ID>"
event = nylas.messages.find_scheduled_message( grant_id, schedule_id,)
print(event)require 'nylas'
# Initialize Nylas clientnylas = Nylas::Client.new( api_key: "<NYLAS_API_KEY>")
messages, _ = nylas.messages.find_scheduled_messages( identifier: "<NYLAS_GRANT_ID>", schedule_id: "<SCHEDULE_ID>")
messages.each {|message| puts message}import com.nylas.NylasClient;import com.nylas.models.*;
public class ReturnMessage { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
Response<ScheduledMessage> message = nylas.messages().findScheduledMessage( "<NYLAS_GRANT_ID>", "<SCHEDULED_MESSAGE_ID>");
System.out.println(message.getData()); }}import com.nylas.NylasClient
fun main(args: Array<String>) { val nylas: NylasClient = NylasClient( apiKey = "<NYLAS_API_KEY>" )
val messages = nylas.messages().findScheduledMessage( "<NYLAS_GRANT_ID>", "<SCHEDULED_MESSAGE_ID").data
print(messages)}nylas email scheduled show <SCHEDULE_ID>Cancel a scheduled message
Section titled “Cancel a scheduled message”Sometimes, you might decide you don’t want to send a scheduled message. When this happens, make a Cancel Scheduled Message request at least 10 seconds before the send_at time.
curl --request DELETE \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/schedules/<SCHEDULE_ID>' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'{ "Succesfully Request Schedule Deletion": { "value": { "request_id": "8cd56334-6d95-432c-86d1-c5dab0ce98be", "data": { "message": "requested cancelation for workflow" } } }}import Nylas from "nylas";
const NylasConfig = { apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",};
const nylas = new Nylas(NylasConfig);
async function deleteMessageSchedule() { try { const result = await nylas.messages.stopScheduledMessage({ identifier: "<NYLAS_GRANT_ID>", scheduleId: "<SCHEDULE_ID>", });
console.log("Result:", result); } catch (error) { console.error("Error deleting message:", error); }}
deleteMessageSchedule();import sysfrom nylas import Client
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>")
grant_id = "<NYLAS_GRANT_ID>"schedule_id = "<SCHEDULE_ID>"
result = nylas.messages.stop_scheduled_message( grant_id, schedule_id,)
print(result)require 'nylas'
# Initialize Nylas clientnylas = Nylas::Client.new( api_key: "<NYLAS_API_KEY>")
messages, _ = nylas.messages.stop_scheduled_messages( identifier: "<NYLAS_GRANT_ID>", schedule_id: "<SCHEDULE_ID>")
messages.each {|message| puts message}import com.nylas.NylasClient;import com.nylas.models.*;
public class ReturnMessage { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
Response<StopScheduledMessageResponse> message = nylas.messages().stopScheduledMessage( "<NYLAS_GRANT_ID>", "SCHEDULED_MESSAGE_ID", null);
System.out.println(message.getData()); }}import com.nylas.NylasClient
fun main(args: Array<String>) { val nylas: NylasClient = NylasClient( apiKey = "<NYLAS_API_KEY>" )
val messages = nylas.messages().stopScheduledMessage( "<NYLAS_GRANT_ID>", "SCHEDULED_MESSAGE_ID").data
print(messages)}nylas email scheduled cancel <SCHEDULE_ID>