Skip to content

Add scheduled send (deferred delivery) for outbound email #61

@stevenobiajulu

Description

@stevenobiajulu

Summary

The MCP server can create drafts and send messages immediately, but has no way to schedule a message to be delivered at a future time. Users who want "send Friday at 8 AM" or "hold this until tomorrow morning so it lands at the top of the recipient's inbox" currently have to either click around the Outlook UI manually or roll their own Graph calls.

This is a common pattern when an agent assists with outbound communication on a deadline (e.g., "draft this and queue it for tomorrow morning, but I want a chance to cancel if I get more info"). It's also useful for automated workflows that produce a draft now but want it delivered in a specific time window.

Proposed surface

Extend the existing send_email and send_draft tools with an optional argument:

scheduled_send_at: string  // ISO 8601 UTC timestamp, e.g., "2026-04-29T12:00:00Z"

When set, the message is queued for delivery at that time instead of being sent immediately.

A complementary tool would help round out the capability:

  • cancel_scheduled_send(message_id) — cancel a queued send (Outlook keeps the queued message in Drafts; this would either delete the draft or strip the deferred-send property)
  • list_scheduled_sends() — list pending queued sends so users can see what's about to go out

Mechanism (Microsoft Graph / Outlook)

Two-step pattern using the MAPI PR_DEFERRED_SEND_TIME extended property (tag 0x3FEF, type SystemTime):

  1. PATCH the draft to set the deferred-send property:
    PATCH /users/{user}/messages/{draftId}
    Content-Type: application/json
    
    {
      "singleValueExtendedProperties": [
        {"id": "SystemTime 0x3FEF", "value": "<ISO-8601 UTC>"}
      ]
    }
  2. POST /users/{user}/messages/{draftId}/send to trigger send. Outlook holds the message in Drafts until the deferred time, then moves it to Sent Items at delivery.

Cancelling: DELETE /users/{user}/messages/{draftId} while it's still in Drafts. The Outlook web/desktop UI exposes this as a "Cancel Send" button on the queued message.

Verification: GET /users/{user}/messages/{draftId}?\$expand=singleValueExtendedProperties(\$filter=id eq 'SystemTime 0x3FEF') returns the property value plus isDraft: true until the scheduled time fires.

Mechanism (Gmail)

The Gmail API does not publicly expose scheduled-send. The Gmail web UI offers it but the underlying mechanism isn't documented in the public Gmail API. For the Gmail provider, the right initial behavior is probably to return a clear "not supported on this provider" error if `scheduled_send_at` is set, so callers can fall back to their own scheduling layer.

Notes for implementation

  • Validate `scheduled_send_at` is in the future and within whatever maximum window Outlook accepts (in practice Outlook allows scheduling years out).
  • The deferred property is set on the draft, then `/send` is called — the order matters. PATCH first, send second.
  • Returned message ID stays valid post-send for retrieval and cancellation while the message is still in Drafts.
  • If the same draft is sent without the property, immediate-send semantics are preserved (no behavior change for existing callers).

Suggested test coverage

  • Unit: provider-microsoft mock returns 200/202 for both PATCH and /send; assert the request bodies and order.
  • Unit: provider-gmail returns a clear unsupported-feature error when `scheduled_send_at` is set.
  • Integration: end-to-end against a real Microsoft mailbox — schedule a send 2 minutes out, confirm it lands in Drafts with the deferred property, then either let it fire or cancel via DELETE.

Related

Verified working against a real Outlook mailbox today (2026-04-28) using the two-step pattern above; Outlook UI correctly shows the queued message in Drafts with a "Cancel Send" button until the scheduled time fires.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions