Skip to content

Documentation navigation

Email (IMAP/SMTP)

IMAP and SMTP toolsets.

#Email connectors (IMAP + SMTP)

Two complementary connectors cover generic email workflows:

  • IMAPToolSet — read and modify an IMAP mailbox.
  • SMTPToolSet — outbound delivery and connection validation via SMTP.

Both use stdlib clients (imaplib, smtplib) and accept a custom client_factory so tests can plug in fakes without opening real network connections.

Agent-ready pattern. IMAPToolSet.search_messages returns compact human-readable summaries by default with a stable message_ref field and the UID kept inline (the UID is the natural handle for fetch_message / mark_read / delete_message). Write tools (set_flags, mark_read, mark_unread, copy_message, move_message, delete_message) accept either a raw UID int, a numeric string, or the message-summary dict from search_messages. The SMTP connector exposes one effective send tool (send_email) plus validate_connection and send_raw_email; sending is destructive because outbound email cannot be unsent.

#IMAPToolSet

Python
from maivn import Agent
from maivn_tools import IMAPToolSet, register_connector

connector = IMAPToolSet(
    host="imap.example.com",
    username="ops@example.com",
    password=secrets["IMAP_PASSWORD"],
    port=993,
    use_ssl=True,
)
agent = Agent(name="mail-agent", model="auto")
register_connector(agent, connector)

#Tools

ToolPermissionDescription
list_mailboxes(pattern)READReturn mailbox names matching pattern.
mailbox_status(mailbox, items)READSTATUS counters (MESSAGES/UNSEEN/UIDNEXT/...).
search_messages(mailbox, criteria, limit, include_metadata)READSearch via IMAP SEARCH; compact message_ref summaries by default.
fetch_message(uid, mailbox)READParsed headers, body, and attachment metadata for one UID. Accepts a UID, numeric string, or message dict.
capability()READServer-advertised IMAP capability tokens.
noop()READRound-trip NOOP for liveness / refresh.
set_flags(uid, mailbox, add, remove, replace)WRITEAdd/remove/replace flags on a UID via STORE. Accepts UID or message dict.
mark_read(uid, mailbox)WRITEConvenience: add \Seen. Accepts UID or message dict.
mark_unread(uid, mailbox)WRITEConvenience: remove \Seen. Accepts UID or message dict.
copy_message(uid, destination, mailbox)WRITECOPY a UID to another mailbox. Accepts UID or message dict.
move_message(uid, destination, mailbox)WRITECOPY + \Deleted + EXPUNGE (works without MOVE). Accepts UID or message dict.
append_message(mailbox, raw_message, flags)WRITEAPPEND a raw RFC 822 message.
create_mailbox(name)WRITECreate a new folder.
rename_mailbox(old_name, new_name)WRITERename a folder.
subscribe_mailbox(name)WRITEServer-side subscription bookmark.
unsubscribe_mailbox(name)WRITERemove a subscription bookmark.
delete_message(uid, mailbox)DELETE (destructive)Set \Deleted and EXPUNGE. Accepts UID or message dict.
expunge(mailbox)DELETE (destructive)EXPUNGE everything currently flagged \Deleted.
delete_mailbox(name)DELETE (destructive)Delete a folder. Server-final.

search_messages accepts any IMAP SEARCH criteria (UNSEEN, FROM "alice@example.com", etc.).

#Agent-ready behavior

search_messages returns compact summaries by default: each match gets a response-local message_ref (message_1, message_2, ...), uid, sender, subject, received_at (Date header), and flags. Summary mode caps limit at 10 because each UID requires a per-message ENVELOPE fetch — for raw UID lists, pass include_metadata=False to get the legacy wire format ({"uids": [...]}). Default limit is

  1. The UID is intentionally exposed in summary mode because every follow-up tool needs it.
Python
imap.search_messages(criteria="UNSEEN")
# { "mailbox": "INBOX",
#   "messages": [{"message_ref": "message_1", "uid": 4242,
#                  "sender": "Alice <alice@example>",
#                  "subject": "Q3 plan", "received_at": "...",
#                  "flags": []}], "totalMatched": 7,
#   "requestedLimit": 25, "summaryLimit": 10 }

# Raw UID list, no per-message fetch.
imap.search_messages(criteria="UNSEEN", include_metadata=False)
# { "mailbox": "INBOX", "uids": [4242, 4243, 4244, ...] }

fetch_message, set_flags, mark_read, mark_unread, copy_message, move_message, and delete_message all accept either a raw UID int, a numeric string, or a message dict from search_messages — the UID is extracted automatically.

#Destructive tools

delete_message, expunge, and delete_mailbox are tagged destructive=True and remove messages or folders server-side without recovery. Filter them out with exclude_tags=["destructive"] for triage-only agents.

#Filtering at registration

Python
# Read-only IMAP for a summarization agent.
agent.add_toolset(connector, include_tags=["read"])

# Read + write but never destructive.
agent.add_toolset(connector, exclude_tags=["destructive"])

#SMTPToolSet

Python
from maivn_tools import SMTPToolSet, register_connector

sender = SMTPToolSet(
    host="smtp.example.com",
    port=587,
    use_ssl=False,
    starttls=True,
    username="ops@example.com",
    password=secrets["SMTP_PASSWORD"],
    sender="ops@example.com",
)
agent = Agent(name="smtp-agent", model="auto")
register_connector(agent, sender)

#Tools

ToolPermissionDescription
validate_connection()READEHLO/login/NOOP/quit; returns server greeting summary.
send_email(to, subject, body_text, body_html, cc, bcc, attachments, sender, reply_to)WRITEBuild a MIME message and send via send_message.
send_raw_email(raw_message, recipients, sender)WRITESend a pre-built MIME via sendmail.

send_email returns:

JSON
{
  "accepted": ["user@example.com"],
  "refused": {"bad@example.com": [550, "User unknown"]},
  "subject": "Hello",
  "to": ["user@example.com"]
}

Attachment content must be bytes (or a string, which is encoded as UTF-8).

#Agent-ready behavior

The SMTP surface is intentionally narrow: there are no list/search tools because SMTP is send-only. validate_connection is the cheap read tool for a startup sanity check. Both send_email and send_raw_email should be treated as high-impact writes — once an email leaves the server it cannot be unsent.

#Destructive tools

The connector does not currently tag send_email / send_raw_email as destructive=True (they remain plain WRITE), but the effect is irreversible. Always confirm the recipient list with the user before calling in an interactive loop. If you need to drop send tools at registration, use include_tags=["read"] to keep only validate_connection.

#Configuration

ArgumentDefaultPurpose
hostrequired (default factory)SMTP server hostname.
port465TCP port. Use 587 for STARTTLS.
use_ssl / starttlsTrue / FalseMutually exclusive transport flags.
username / password""Auth credentials. Skip auth by leaving empty.
senderrequiredDefault From address.
client_factoryNoneOverride the SMTP client (used in tests).