# retoor <retoor@molodetz.nl>
from __future__ import annotations
from .spec import Action, Param
REMOTE_NOTE = (
"This reaches the user's OWN external mailbox over IMAP/SMTP, not this platform. The "
"account must be configured first with email_account_set."
)
def arg(
name: str, description: str, required: bool = False, kind: str = "string"
) -> Param:
return Param(
name=name,
location="body",
description=description,
required=required,
type=kind,
)
ACCOUNT = arg(
"account",
"The label of the configured email account to use (see email_accounts_list).",
required=True,
)
FOLDER = arg("folder", "Mailbox folder; defaults to INBOX when omitted.")
UID = arg("uid", "The message UID returned by email_list_messages / email_search.", required=True)
FILTERS = (
arg("unseen", "Only unread messages.", kind="boolean"),
arg("seen", "Only read messages.", kind="boolean"),
arg("flagged", "Only flagged messages.", kind="boolean"),
arg("from", "Match the sender address or name."),
arg("subject", "Match text in the subject."),
arg("since", "Only messages on or after this date, formatted DD-Mon-YYYY (e.g. 01-Jan-2026)."),
arg("text", "Match text anywhere in the message."),
)
EMAIL_ACTIONS: tuple[Action, ...] = (
Action(
name="email_accounts_list",
method="LOCAL",
path="",
summary="List the user's configured email accounts (passwords are never returned)",
description="Returns each saved account's connection settings without the password.",
handler="email",
requires_auth=True,
read_only=True,
),
Action(
name="email_account_get",
method="LOCAL",
path="",
summary="Read one configured email account's connection settings",
description="Returns the account settings without the password (password_set indicates whether one is stored).",
handler="email",
requires_auth=True,
read_only=True,
params=(ACCOUNT,),
),
Action(
name="email_account_set",
method="LOCAL",
path="",
summary="Create or update an email account connection (IMAP + SMTP)",
description=(
"Saves an IMAP/SMTP connection under a label. Sensible defaults are applied when a "
"field is omitted: imap_port 993 with imap_ssl on, smtp_port 587 with smtp_starttls on, "
"from_address falls back to username. Provide host, username and password at minimum. "
+ REMOTE_NOTE
),
handler="email",
requires_auth=True,
params=(
ACCOUNT,
arg("imap_host", "IMAP server hostname (e.g. imap.gmail.com)."),
arg("imap_port", "IMAP port (default 993).", kind="integer"),
arg("imap_ssl", "Use implicit SSL/TLS for IMAP (default true).", kind="boolean"),
arg("imap_starttls", "Upgrade a plain IMAP connection with STARTTLS (default false).", kind="boolean"),
arg("smtp_host", "SMTP server hostname (e.g. smtp.gmail.com)."),
arg("smtp_port", "SMTP port (default 587).", kind="integer"),
arg("smtp_ssl", "Use implicit SSL/TLS for SMTP (default false; set true for port 465).", kind="boolean"),
arg("smtp_starttls", "Upgrade a plain SMTP connection with STARTTLS (default true).", kind="boolean"),
arg("username", "Login username (usually the full email address)."),
arg("password", "Login password or app-specific password."),
arg("from_address", "From address for sent mail (defaults to the username)."),
arg("from_name", "Display name for sent mail."),
),
),
Action(
name="email_account_delete",
method="LOCAL",
path="",
summary="Delete a saved email account connection (confirmation required)",
description="Removes the stored connection. Does not touch the remote mailbox.",
handler="email",
requires_auth=True,
params=(
ACCOUNT,
arg(
"confirm",
"Set to true only after the user has confirmed the deletion.",
required=True,
kind="boolean",
),
),
),
Action(
name="email_list_folders",
method="LOCAL",
path="",
summary="List the mailbox folders on the account",
description=REMOTE_NOTE,
handler="email",
requires_auth=True,
read_only=True,
params=(ACCOUNT,),
),
Action(
name="email_list_messages",
method="LOCAL",
path="",
summary="List messages in a folder, newest first, with optional filters",
description=(
"Returns message summaries (uid, from, subject, date, flags). Use the filters to narrow "
"the set and limit/offset to page. " + REMOTE_NOTE
),
handler="email",
requires_auth=True,
read_only=True,
params=(
ACCOUNT,
FOLDER,
*FILTERS,
arg("limit", "Maximum messages to return (1-100, default 25).", kind="integer"),
arg("offset", "Number of messages to skip for paging (default 0).", kind="integer"),
),
),
Action(
name="email_read_message",
method="LOCAL",
path="",
summary="Read the full body, headers and attachment list of one message",
description="Fetches the decoded text/html body and attachment metadata. " + REMOTE_NOTE,
handler="email",
requires_auth=True,
read_only=True,
params=(ACCOUNT, FOLDER, UID),
),
Action(
name="email_search",
method="LOCAL",
path="",
summary="Search a folder by sender, subject, date, text or read/flag state",
description="Returns matching message summaries. " + REMOTE_NOTE,
handler="email",
requires_auth=True,
read_only=True,
params=(
ACCOUNT,
FOLDER,
*FILTERS,
arg("limit", "Maximum messages to return (1-100, default 25).", kind="integer"),
),
),
Action(
name="email_mark",
method="LOCAL",
path="",
summary="Mark a message read, unread, flagged or unflagged",
description="Convenience wrapper over the IMAP \\Seen and \\Flagged flags. " + REMOTE_NOTE,
handler="email",
requires_auth=True,
params=(
ACCOUNT,
FOLDER,
UID,
arg("state", "One of: read, unread, flagged, unflagged.", required=True),
),
),
Action(
name="email_set_flags",
method="LOCAL",
path="",
summary="Add or remove arbitrary IMAP flags on a message",
description="For advanced use; prefer email_mark for read/flagged. " + REMOTE_NOTE,
handler="email",
requires_auth=True,
params=(
ACCOUNT,
FOLDER,
UID,
arg("flags", "Comma-separated IMAP flags (e.g. \\Seen, \\Flagged, \\Answered).", required=True),
arg("add", "true to add the flags, false to remove them (default true).", kind="boolean"),
),
),
Action(
name="email_move_message",
method="LOCAL",
path="",
summary="Move a message to another folder",
description="Copies the message to the destination folder and removes it from the source. " + REMOTE_NOTE,
handler="email",
requires_auth=True,
params=(
ACCOUNT,
FOLDER,
UID,
arg("destination", "Destination folder name.", required=True),
),
),
Action(
name="email_delete_message",
method="LOCAL",
path="",
summary="Delete a message (confirmation required)",
description=(
"Deletes the message. If 'trash' is given the message is moved there; otherwise it is "
"flagged \\Deleted and expunged. " + REMOTE_NOTE
),
handler="email",
requires_auth=True,
params=(
ACCOUNT,
FOLDER,
UID,
arg("trash", "Optional Trash folder to move the message into instead of expunging."),
arg(
"confirm",
"Set to true only after the user has confirmed the deletion.",
required=True,
kind="boolean",
),
),
),
Action(
name="email_send",
method="LOCAL",
path="",
summary="Send an email via the account's SMTP server",
description="Composes and sends a message. " + REMOTE_NOTE,
handler="email",
requires_auth=True,
params=(
ACCOUNT,
arg("to", "Recipient address or comma-separated list.", required=True),
arg("subject", "Email subject."),
arg("body", "Plain-text body."),
arg("cc", "CC recipients (comma-separated)."),
arg("bcc", "BCC recipients (comma-separated)."),
arg("html", "Optional HTML alternative body."),
arg("in_reply_to", "Optional Message-ID this email replies to."),
),
),
)