id: microsoft_exchange_mail name: Microsoft Exchange Online Mail version: 1.0.0 description: "Send email from an Exchange Online mailbox through Microsoft Graph — plain-text and HTML bodies (inline data-URI images embedded), attachments of any size, CC/BCC, Reply-To and importance; reply, reply-all and forward an existing message. App-only OAuth 2.0 (tenant ID, client ID, client secret); stdlib-only, no extra Python dependencies." changelog: "1.0.0 — Initial release: send-mail (text/HTML, inline images, attachments, CC/BCC/Reply-To, importance), reply-mail, forward-mail and a connection test that reads the permissions granted to the app." category: email # Per-instance configuration. App-only (client-credentials) OAuth 2.0 against # Microsoft Graph. The app registration needs the Graph *application* # permission Mail.Send, with admin consent. Mail.Send lets the app send as any # mailbox of the tenant: restrict it to the sending mailbox(es) with an Exchange # application access policy (New-ApplicationAccessPolicy -AccessRight # RestrictAccess) or RBAC for Applications. # Two optional permissions widen what the commands accept: # - Mail.ReadWrite: attachments over 3 MB in total (draft + upload session). # - Mail.ReadBasic.All: reply/forward found by internet_message_id instead of # the Graph message id. config_schema: properties: tenant_id: type: string description: "Directory (tenant) ID of the app registration" client_id: type: string description: "Application (client) ID of the app registration" client_secret: type: string description: "Client secret value of the app registration" x-soar-sensitive: true sender_mailbox: type: string description: "Default sending mailbox (UPN or SMTP address, e.g. soc@contoso.com) — commands may override it with `mailbox`" save_to_sent_items: type: boolean description: "Keep a copy of sent mail in the mailbox's Sent Items" default: true required: - tenant_id - client_id - client_secret - sender_mailbox commands: - id: send_mail name: exchange-send-mail description: "Send an email from the configured mailbox (or `mailbox`). At least one recipient (to/cc/bcc) is required. Inline data-URI images in html_body are embedded; attachments over 3 MB in total go through a draft and need Mail.ReadWrite." risk: safe_write inputs_schema: properties: to: { type: string, description: "Recipient(s) for the To field, comma- or semicolon-separated" } cc: { type: string, description: "CC recipient(s), comma- or semicolon-separated" } bcc: { type: string, description: "BCC recipient(s), comma- or semicolon-separated" } subject: { type: string, description: "Subject" } body: { type: string, description: "Plain-text body (ignored when html_body is given)" } html_body: { type: string, description: "HTML body (inline data-URI images are extracted and embedded)" } reply_to: { type: string, description: "Reply-To address(es), comma-separated" } importance: { type: string, enum: [low, normal, high], description: "Message importance (default normal)" } mailbox: { type: string, description: "Send from this mailbox instead of the configured one" } attachment_name: { type: string, description: "Attachment file name (with attachment_base64)" } attachment_base64: { type: string, description: "Attachment content, base64-encoded" } attachments: { type: string, description: "Several attachments as a JSON array: [{\"name\": \"a.pdf\", \"content_base64\": \"...\", \"content_type\": \"application/pdf\"}]" } save_to_sent_items: { type: boolean, description: "Override the instance setting for this message" } required: [] outputs_schema: properties: sent: { type: boolean } mailbox: { type: string } recipients: { type: array } attachments: { type: number } - id: reply_mail name: exchange-reply-mail description: "Reply (or reply to all) to a message of the mailbox. Give the Graph message_id, or the internet_message_id (needs Mail.ReadBasic.All). The original message is quoted below the reply by Exchange." risk: safe_write inputs_schema: properties: message_id: { type: string, description: "Graph id of the message to answer" } internet_message_id: { type: string, description: "Internet Message-ID header of the message (<...@...>), used when message_id is empty" } body: { type: string, description: "Plain-text reply" } html_body: { type: string, description: "HTML reply (takes precedence over body)" } reply_all: { type: boolean, description: "Answer every recipient of the original message (default false)" } cc: { type: string, description: "Extra CC recipient(s), comma-separated" } mailbox: { type: string, description: "Mailbox holding the message (default: the configured one)" } required: [] outputs_schema: properties: sent: { type: boolean } message_id: { type: string } - id: forward_mail name: exchange-forward-mail description: "Forward a message of the mailbox, with an optional comment above it. Give the Graph message_id, or the internet_message_id (needs Mail.ReadBasic.All)." risk: safe_write inputs_schema: properties: message_id: { type: string, description: "Graph id of the message to forward" } internet_message_id: { type: string, description: "Internet Message-ID header of the message (<...@...>), used when message_id is empty" } to: { type: string, description: "Recipient(s), comma- or semicolon-separated" } comment: { type: string, description: "Text placed above the forwarded message" } mailbox: { type: string, description: "Mailbox holding the message (default: the configured one)" } required: [to] outputs_schema: properties: sent: { type: boolean } message_id: { type: string } - id: test_connection name: exchange-test-connection description: "Obtain a Graph token and check the app holds Mail.Send, without sending mail (used by the Test button). Reports the optional permissions too." risk: read inputs_schema: properties: {} required: [] outputs_schema: { properties: {} }