Scheduled Emails
Schedule emails for future delivery with timezone support
Schedule an Email
POST https://api.mail.land/v1/transactional/scheduledcurl -X POST https://api.mail.land/v1/transactional/scheduled \
-H "X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD..." \
-H "Content-Type: application/json" \
-d '{
"from": {
"email": "[email protected]",
"name": "Your App"
},
"to": {
"email": "[email protected]",
"name": "Jane Doe"
},
"subject": "Your trial expires tomorrow",
"html": "<h1>Hi Jane,</h1><p>Your free trial expires tomorrow. Upgrade now to keep access.</p>",
"text": "Hi Jane, Your free trial expires tomorrow. Upgrade now to keep access.",
"scheduledAt": "2026-02-18T09:00:00Z",
"timeZone": "America/New_York",
"tags": ["trial-expiry", "reminder"]
}'Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
from | object | Yes | Sender address (email and optional name) |
to | object | Yes | Single recipient (email and optional name) |
subject | string | Yes | Email subject line |
html | string | Conditional | HTML content (one of html or text required) |
text | string | Conditional | Plain text content |
replyTo | string | No | Reply-to address |
cc | array | No | CC recipients (email and optional name) |
bcc | array | No | BCC recipients (email and optional name) |
attachments | array | No | File attachments (same format as single send) |
headers | object | No | Custom email headers |
tags | array | No | Tags for analytics |
scheduledAt | datetime | Yes | When to send. Always interpreted as UTC: send it with a Z or an explicit offset |
timeZone | string | No | IANA timezone (e.g., America/New_York). Stored and echoed back for display only: it does not affect when the email is sent |
scheduledAt must be in the future and no more than 7 days ahead. Setting timeZone does not convert scheduledAt into that zone: the timestamp is always read as UTC. A past time is rejected with 400 VALIDATION_FAILED; more than 7 days ahead is rejected with 400 INVALID_REQUEST ("Cannot schedule email more than 7 days in advance"). The same 7-day window applies whether the schedule is created here, via the send endpoint, or via reschedule.
Note the field name differences from the send endpoint: scheduled emails use html/text (not htmlContent/textContent) and to is a single object (not an array).
Response
Status: 201 Created
{
"scheduleId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "pending",
"scheduledAt": "2026-02-18T09:00:00Z",
"timeZone": "America/New_York",
"to": "[email protected]",
"recipientCount": 1,
"subject": "Your trial expires tomorrow",
"createdAt": "2026-02-17T10:00:00Z",
"processedAt": null,
"messageIds": [],
"rejectedRecipients": [],
"reasonCode": null,
"error": null
}Scheduled Email Statuses
| Status | Description |
|---|---|
pending | Queued for future delivery |
processing | Being processed for sending |
sent | Successfully sent |
failed | Sending failed (check error field) |
cancelled | Cancelled before sending |
List Scheduled Emails
GET https://api.mail.land/v1/transactional/scheduled?limit=20curl https://api.mail.land/v1/transactional/scheduled?limit=20 \
-H "X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD..."Filter by status:
GET https://api.mail.land/v1/transactional/scheduled?status=pendingcurl "https://api.mail.land/v1/transactional/scheduled?status=pending" \
-H "X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD..."{
"items": [
{
"scheduleId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "pending",
"scheduledAt": "2026-02-18T09:00:00Z",
"timeZone": "America/New_York",
"to": "[email protected]",
"recipientCount": 1,
"subject": "Your trial expires tomorrow",
"createdAt": "2026-02-17T10:00:00Z",
"processedAt": null,
"messageIds": [],
"rejectedRecipients": [],
"reasonCode": null,
"error": null
}
],
"nextCursor": "eyJ2IjoxLCJzIjoiY3JlYXRlZGF0OmRlc2MiLCJrIjoi...",
"hasMore": true
}Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
cursor | string | - | Opaque cursor from the previous page's nextCursor. Omit for the first page. |
limit | int | 20 | Items per page (max 100) |
status | string | - | Filter by status: pending, processing, sent, failed, cancelled |
Get a Scheduled Email
GET https://api.mail.land/v1/transactional/scheduled/{scheduleId}curl https://api.mail.land/v1/transactional/scheduled/f47ac10b-58cc-4372-a567-0e02b2c3d479 \
-H "X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD..."{
"scheduleId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "sent",
"scheduledAt": "2026-02-18T09:00:00Z",
"timeZone": "America/New_York",
"to": "[email protected]",
"recipientCount": 1,
"subject": "Your trial expires tomorrow",
"createdAt": "2026-02-17T10:00:00Z",
"processedAt": "2026-02-18T09:00:02Z",
"messageId": "t_a1b2c3d4e5f6...",
"messageIds": ["t_a1b2c3d4e5f6..."],
"rejectedRecipients": [],
"reasonCode": null,
"error": null
}After sending, the response includes processedAt (when it was sent), messageId (legacy single-message field), messageIds and rejectedRecipients (the full record of what was delivered or dropped), and reasonCode/error when the schedule failed instead.
Cancel a Scheduled Email
DELETE https://api.mail.land/v1/transactional/scheduled/{scheduleId}curl -X DELETE https://api.mail.land/v1/transactional/scheduled/f47ac10b-58cc-4372-a567-0e02b2c3d479 \
-H "X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD..."Returns 204 No Content on success. You can only cancel emails with pending status. Attempting to cancel a processing, sent, or failed email returns 400 Bad Request.
Reschedule an Email
Change the delivery time of a pending email:
PATCH https://api.mail.land/v1/transactional/scheduled/{scheduleId}curl -X PATCH https://api.mail.land/v1/transactional/scheduled/f47ac10b-58cc-4372-a567-0e02b2c3d479 \
-H "X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD..." \
-H "Content-Type: application/json" \
-d '{
"scheduledAt": "2026-02-19T14:00:00Z",
"timeZone": "Europe/London"
}'{
"scheduleId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "pending",
"scheduledAt": "2026-02-19T14:00:00Z",
"timeZone": "Europe/London",
"to": "[email protected]",
"recipientCount": 1,
"subject": "Your trial expires tomorrow",
"createdAt": "2026-02-17T10:00:00Z",
"processedAt": null,
"messageIds": [],
"rejectedRecipients": [],
"reasonCode": null,
"error": null
}The same scheduling constraints apply: the new time must be in the future and no more than 7 days from now, or the request is rejected with 400 INVALID_REQUEST.
Timezone Handling
scheduledAt is always interpreted as UTC, whether or not you provide a timeZone: send it with a Z or an explicit offset. timeZone is stored and echoed back in responses for display purposes only; MailLand does not convert scheduledAt into that zone. Use IANA timezone identifiers if you want it recorded for display:
America/New_YorkEurope/LondonAsia/TokyoAustralia/Sydney
Scheduling via the Send Endpoint
You can also schedule emails directly through the regular send endpoint by including the scheduledAt field:
{
"from": { "email": "[email protected]" },
"to": [{ "email": "[email protected]" }],
"subject": "Scheduled email",
"htmlContent": "<p>This will be sent later.</p>",
"scheduledAt": "2026-02-18T09:00:00Z"
}The 202 response from the send endpoint includes scheduleId and scheduledAt. That schedule is the same kind of row this page's endpoints operate on: it can be listed, fetched, cancelled and rescheduled with the endpoints described above, exactly like a schedule created directly through /v1/transactional/scheduled. The 7-day window (see the callout above) applies here too: scheduledAt more than 7 days ahead is rejected with 400 INVALID_REQUEST.
Next Steps
- REST API: immediate email sending
- Templates: use templates via
/v1/transactional/sendwithscheduledAt - Batch Sending: schedule batch sends