AI agents determine the best send window; humans approve before delivery. Scheduled email that doesn't mean unreviewed email.
Scheduled email delivery is useful precisely because it decouples writing from sending. But when an agent drafts a message days before it goes out, that gap creates risk: context may have changed, the tone may be off, or the recipient situation may have shifted. A message sitting in a send queue until it fires automatically is a message that bypasses the review window operators actually wanted. Most scheduling tools treat the schedule as final — once queued, the message goes out unless someone manually intervenes. Agents can generate high volumes of scheduled messages, which makes manual cancellation impractical and silent delivery the default. At scale, this means unreviewed outbound email is the norm rather than the exception.
MultiMail's send_email endpoint accepts a scheduled_at timestamp, queuing the message for future delivery. Scheduling and gated_send oversight work together: a scheduled message requires human approval before it releases, not after the timer fires. Operators see the message, its intended send time, and full context during the review window — not a race against the clock. list_pending surfaces all queued messages with their scheduled times and approval status. cancel_message removes any message from the queue before delivery, giving operators a clean intervention path without digging through logs or reversing a sent message.
The agent calls send_email with a scheduled_at ISO 8601 timestamp — derived from recipient timezone data, engagement patterns, or business rules. The message is queued but not yet approved or delivered.
list_pending returns all scheduled messages with their send times, recipients, subjects, and status. Operators have full visibility into the outbox before anything goes out.
Under gated_send, MultiMail fires an approval webhook when a scheduled message requires review. The operator receives the message content and intended send time together — context and timing in a single decision.
The operator calls decide_email to approve or reject, or cancel_message to remove the message entirely. Nothing delivers until the gate is cleared. If context changed since drafting, cancel_message is available at any point prior to delivery.
Once approved, the message holds in queue until the scheduled_at timestamp, then delivers. Delivery and open events flow back via webhook, giving the agent feedback to refine future send-time selection.
import httpx
from datetime import datetime, timezone, timedelta
"cm"># Schedule for tomorrow at 9 AM UTC
send_at = (datetime.now(timezone.utc) + timedelta(days=1)).replace(
hour=9, minute=0, second=0, microsecond=0
)
response = httpx.post(
"https://api.multimail.dev/v1/send_email",
headers={"Authorization": "Bearer $MULTIMAIL_API_KEY"},
json={
"from": "[email protected]",
"to": "[email protected]",
"subject": "Scheduled follow-up for tomorrow at 9:00 AM",
"body": "Following up on our conversation — wanted to share the report while it&"cm">#039;s fresh for your morning review.",
"scheduled_at": send_at.isoformat(),
"oversight_mode": "gated_send"
}
)
data = response.json()
print(f"Queued: {data[&"cm">#039;message_id']}")
print(f"Scheduled for: {data[&"cm">#039;scheduled_at']}")
print(f"Status: {data[&"cm">#039;status']}") # 'pending_approval' until reviewedSchedule a message for future delivery with gated_send oversight. The message enters the pending queue and requires human approval before the send window opens.
import httpx
client = httpx.Client(
base_url="https://api.multimail.dev/v1",
headers={"Authorization": "Bearer $MULTIMAIL_API_KEY"}
)
"cm"># Show all pending and scheduled messages
pending = client.get("/list_pending").json()
for msg in pending["messages"]:
print(f"[{msg[&"cm">#039;message_id']}] To: {msg['to']}")
print(f" Subject: {msg[&"cm">#039;subject']}")
print(f" Scheduled: {msg[&"cm">#039;scheduled_at']}")
print(f" Status: {msg[&"cm">#039;status']}")
print()
# Cancel a specific message if the situation changed before delivery
message_id = "msg_01HX3J9KQRST456"
cancel_resp = client.post(
"/cancel_message",
json={
"message_id": message_id,
"reason": "Deal closed before send window — no longer relevant"
}
)
print(f"Cancelled: {cancel_resp.json()[&"cm">#039;cancelled']}")Retrieve all scheduled messages awaiting delivery or approval. Use cancel_message to remove a message before it fires if context has changed.
from multimail_sdk import MultiMailClient
from datetime import datetime, timedelta
import pytz
client = MultiMailClient(api_key="$MULTIMAIL_API_KEY")
def schedule_for_recipient_morning(
to_email: str,
recipient_tz: str,
subject: str,
body: str
) -> dict:
tz = pytz.timezone(recipient_tz)
now_local = datetime.now(tz)
"cm"># Target next-day 9 AM in recipient's timezone
target = now_local.replace(hour=9, minute=0, second=0, microsecond=0)
if target <= now_local:
target += timedelta(days=1)
send_at_utc = target.astimezone(pytz.utc)
return client.send_email(
from_address="[email protected]",
to=to_email,
subject=subject,
body=body,
scheduled_at=send_at_utc.isoformat(),
oversight_mode="gated_send"
)
result = schedule_for_recipient_morning(
to_email="[email protected]",
recipient_tz="America/Chicago",
subject="Proposal follow-up — three questions before your review",
body="Wanted to flag three open items in the proposal before your team reviews it Thursday."
)
print(f"Queued {result[&"cm">#039;message_id']} for {result['scheduled_at']}")Use the MultiMail Python SDK to schedule messages for the recipient's local morning delivery window. Useful for outbound sales or account management agents operating across time zones.
from fastapi import FastAPI, Request
import httpx
app = FastAPI()
MULTIMAIL_TOKEN = "$MULTIMAIL_API_KEY"
SAFE_DOMAINS = {"trusted-client.com", "enterprise-prospect.com"}
@app.post("/webhooks/multimail")
async def handle_approval_event(request: Request):
event = await request.json()
if event["type"] != "approval.required":
return {"ok": True}
msg = event["message"]
print(f"Review needed: &"cm">#039;{msg['subject']}' → {msg['to']}")
print(f"Scheduled for: {msg[&"cm">#039;scheduled_at']}")
recipient_domain = msg["to"].split("@")[-1]
auto_approve = recipient_domain in SAFE_DOMAINS
httpx.post(
"https://api.multimail.dev/v1/decide_email",
headers={"Authorization": f"Bearer {MULTIMAIL_TOKEN}"},
json={
"message_id": msg["message_id"],
"decision": "approve" if auto_approve else "reject",
"note": "Auto-approved: matched trusted domain" if auto_approve
else "Routed to human reviewer — unknown domain"
}
)
return {"ok": True}Process approval events from MultiMail before the scheduled send window. Apply rule-based auto-approval or route to a human reviewer depending on message attributes.
import httpx
from datetime import datetime, timezone, timedelta
"cm"># Schedule a reply for 48 hours from now
follow_up_at = datetime.now(timezone.utc) + timedelta(hours=48)
response = httpx.post(
"https://api.multimail.dev/v1/reply_email",
headers={"Authorization": "Bearer $MULTIMAIL_API_KEY"},
json={
"thread_id": "thread_01HX7R2MNPQST789",
"from": "[email protected]",
"body": "Circling back as promised — happy to set up a 30-minute call to walk through the integration options if that&"cm">#039;s useful.",
"scheduled_at": follow_up_at.isoformat(),
"oversight_mode": "gated_send"
}
)
data = response.json()
print(f"Reply queued: {data[&"cm">#039;message_id']}")
print(f"Thread: {data[&"cm">#039;thread_id']}")
print(f"Delivers at: {data[&"cm">#039;scheduled_at']}")Use reply_email with scheduled_at to queue a follow-up that stays in context with the original thread. Useful for support or sales agents managing multi-turn conversations.
gated_send oversight ensures humans review scheduled messages while there's still time to edit or cancel — not after the send window fires. The approval step is part of the scheduling flow, not a race against the clock.
Operators can see every scheduled message, its intended recipient, send time, and current status in a single API call. No digging through logs to understand what's queued or what's about to go out.
cancel_message removes a queued message at any point before delivery. If deal terms change, a meeting moves, or the agent's judgment was wrong, operators have a clear intervention path without infrastructure support.
The gated_send approval step creates a documented review record for each outbound message — supporting CAN-SPAM documentation requirements for commercial email programs that must demonstrate intentional, human-reviewed sending.
Pass any ISO 8601 timestamp to scheduled_at and the API handles delivery at the correct moment. Agents compute optimal send windows from recipient data; MultiMail manages the queue, retries, and delivery confirmation.
Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 50-tool MCP server. Formally verified in Lean 4.