Email

UXC exposes email as an event source plus direct outbound commands. Inbound messages stream into daemon-backed subscriptions as normalized email_event envelopes; replies and new mail go out through uxc email send and uxc email reply over SMTP.

Inbound Subscriptions

Two transports cover self-hosted IMAP mailboxes and hosted providers:

TransportModeTargetExample endpoint
email-imap-idlestreamAny IMAP server with IDLEimaps://imap.example.com:993
email-provider-pollpollGmail, Microsoft Graph, or JMAP HTTP APIshttps://gmail.googleapis.com/gmail/v1/users/me/messages

IMAP IDLE

uxc source ensure agentinbox email:primary imaps://imap.example.com:993 \
  --transport email-imap-idle \
  --auth email-primary mailbox=INBOX

The subscription holds the connection open with IMAP IDLE and emits one email_event per newly seen message. Credentials come from the referenced auth profile (--auth email-primary); store the IMAP username and password in that profile with your preferred auth mechanism.

These subscriptions resolve credentials inside the daemon process, so env-sourced secrets (--secret-env) cannot see variables exported in the shell that runs uxc source ensure. uxc source ensure warns on stderr in that case; see Secret Sources for recommended alternatives such as literal secrets or 1Password references.

Microsoft personal accounts require OAuth (XOAUTH2). Personal Microsoft accounts (outlook.com, hotmail.com, live.com) reject IMAP LOGIN with NO Basic authentication is disabled before validating credentials, so no password fix can make basic auth work for them. UXC detects this server-side policy rejection, reports the source as failed with an actionable error, and stops reconnecting. Use the XOAUTH2 flow below to keep IMAP IDLE push delivery, or fall back to Provider Polling with provider=graph for minute-level polling instead.

Microsoft Personal Accounts (XOAUTH2)

IMAP for personal Microsoft accounts authenticates with OAuth via the XOAUTH2 SASL mechanism. UXC ships a provider preset that borrows the public Thunderbird client id, so a personal account needs two commands and one browser consent — no Azure app registration:

# 1. Device-code login; opens a consent page in the browser
uxc auth oauth login outlook-imap --provider outlook

# 2. Subscribe over IMAP IDLE with the OAuth credential
uxc source ensure agentinbox email:primary imaps://outlook.office365.com:993 \
  --transport email-imap-idle \
  --auth outlook-imap mailbox=INBOX

The --provider outlook preset fills in:

Preset defaultValue
Client idMozilla Thunderbird public client 9e5f94bc-e8a4-4e73-b8be-63364c29d753
Issuer / tenanthttps://login.microsoftonline.com/consumers
Scopeshttps://outlook.office.com/IMAP.AccessAsUser.All, https://outlook.office.com/SMTP.Send, offline_access

Notes:

Provider Polling

uxc source ensure agentinbox gmail:primary \
  https://gmail.googleapis.com/gmail/v1/users/me/messages \
  --transport email-provider-poll --mode poll \
  --auth gmail-primary provider=gmail

provider=gmail|graph|jmap selects the normalization path. The endpoint must return the provider's message-list JSON, so UXC can map it onto the same email_event envelope:

ProviderEndpoint exampleResponse shape
Gmailhttps://gmail.googleapis.com/gmail/v1/users/me/messagesmessages array
Microsoft Graphhttps://graph.microsoft.com/v1.0/me/messages?$expand=attachmentsvalue array
JMAPJMAP API endpoint returning Email/get resultsEmail/get list (POST envelope) or list array (GET gateway)

Microsoft Graph only returns attachment metadata when the endpoint includes $expand=attachments; see Email Attachments for the partial-metadata semantics.

POST-only JMAP endpoints

Standard JMAP servers expose a single POST-only API URL and express requests as methodCalls envelopes. Pass method=post with a body argument holding the JMAP request document; UXC POSTs it each poll cycle and reads the Email/get response from the returned methodResponses envelope:

uxc source ensure agentinbox email:jmap https://jmap.example.com/jmap/api \
  --transport email-provider-poll --mode poll --auth jmap-primary \
  --input-json '{"provider":"jmap","mailbox":"INBOX","method":"POST","body":{
    "using":["urn:ietf:params:jmap:core","urn:ietf:params:jmap:mail"],
    "methodCalls":[
      ["Email/query",{"accountId":null,"sort":[{"property":"receivedAt","isAscending":false}],"position":0,"limit":20},"q0"],
      ["Email/get",{"#ids":{"resultOf":"q0","name":"Email/query","path":"/ids"},
        "properties":["id","blobId","threadId","from","to","subject","preview","receivedAt","keywords","attachments"]},"g0"]
    ]}}' \
  --poll-config '{"interval_secs":60,"extract_items_pointer":"/items","checkpoint_strategy":{"type":"item_key","item_key_pointer":"/message/uid"},"initial_items_limit":0}'

Notes:

Microsoft Graph with Personal Accounts

The Graph mail API (/v1.0/me/messages, delegated Mail.Read) also serves personal Microsoft accounts, so provider=graph polling works for them as a no-IDLE alternative to IMAP XOAUTH2:

Event Envelope

Every transport emits the same envelope shape:

{
  "type": "email_event",
  "version": "v1",
  "provider": "imap",
  "account": "user@example.com",
  "mailbox": "INBOX",
  "event_kind": "message_received",
  "message": {
    "uid": "42",
    "message_id": "<msg@example.com>",
    "thread_id": null,
    "from": "sender@example.com",
    "to": ["user@example.com"],
    "subject": "Quarterly report",
    "date": "Mon, 07 Sep 2026 12:00:00 +0000",
    "snippet": "Please review the attached report...",
    "attachments": [],
    "has_attachments": false,
    "attachment_count": 0,
    "flags": []
  },
  "raw": {
    "mime_inline": null,
    "mime_truncated": true,
    "size_bytes": 204800
  },
  "reply_handle": {
    "type": "email_imap",
    "provider": "imap",
    "account": "user@example.com",
    "mailbox": "INBOX",
    "message_id": "<msg@example.com>",
    "uid": "42"
  }
}

Provider-polled events use provider: "gmail" | "graph" | "jmap", keep the provider's original JSON under raw.provider_payload, and stamp a reply_handle of type email_provider.

FieldNotes
message.attachmentsNeutral attachment metadata plus retrieval handles; see Email Attachments
message.has_attachmentstrue when the message has (or the provider reports) attachments
raw.mime_inlineRaw MIME text inlined when the message is small enough; null otherwise
raw.provider_payloadOriginal provider JSON for polled sources
reply_handleOpaque handle consumed by uxc email reply --reply-handle

Outbound Mail

Send a new message:

uxc email send --smtp smtp://localhost:2525 \
  --from bot@example.com --to user@example.com \
  --subject 'Hello' --text-body 'Hi'

Reply to a received event using its reply_handle:

uxc email reply --smtp smtp://localhost:2525 \
  --reply-handle '{"message_id":"<msg@example.com>"}' \
  --from bot@example.com --to user@example.com \
  --subject 'Re: Quarterly report' --text-body 'Thanks'

Notes:

uxc auth oauth login outlook-imap --provider outlook   # same credential as IMAP

uxc email send --smtp smtp://smtp-mail.outlook.com:587 \
  --from user@hotmail.com --to someone@example.com \
  --subject 'Hello' --text-body 'Hi' \
  --auth outlook-imap

OAuth tokens are never sent over cleartext: smtp:// must offer STARTTLS (Microsoft's submission endpoint does) or use smtps://.