Tools & Connectors
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 customclient_factory so tests can plug in fakes without opening real network
connections.
Agent-ready pattern.IMAPToolSet.search_messagesreturns
compact human-readable summaries by default with a stablemessage_reffield and the UID kept inline (the UID is the natural
handle forfetch_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 fromsearch_messages.
The SMTP connector exposes one effective send tool (send_email)
plusvalidate_connectionandsend_raw_email; sending is
destructive because outbound email cannot be unsent.
#IMAPToolSet
from maivn import Agentfrom 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(model="auto")register_connector(agent, connector)#Tools
| Tool | Permission | Description |
|---|---|---|
list_mailboxes(pattern) |
READ | Return mailbox names matching pattern. |
mailbox_status(mailbox, items) |
READ | STATUS counters (MESSAGES/UNSEEN/UIDNEXT/...). |
search_messages(mailbox, criteria, limit, include_metadata) |
READ | Search via IMAP SEARCH; compact message_ref summaries by default. |
fetch_message(uid, mailbox) |
READ | Parsed headers, body, and attachment metadata for one UID. Accepts a UID, numeric string, or message dict. |
capability() |
READ | Server-advertised IMAP capability tokens. |
noop() |
READ | Round-trip NOOP for liveness / refresh. |
set_flags(uid, mailbox, add, remove, replace) |
WRITE | Add/remove/replace flags on a UID via STORE. Accepts UID or message dict. |
mark_read(uid, mailbox) |
WRITE | Convenience: add \Seen. Accepts UID or message dict. |
mark_unread(uid, mailbox) |
WRITE | Convenience: remove \Seen. Accepts UID or message dict. |
copy_message(uid, destination, mailbox) |
WRITE | COPY a UID to another mailbox. Accepts UID or message dict. |
move_message(uid, destination, mailbox) |
WRITE | COPY + \Deleted + EXPUNGE (works without MOVE). Accepts UID or message dict. |
append_message(mailbox, raw_message, flags) |
WRITE | APPEND a raw RFC 822 message. |
create_mailbox(name) |
WRITE | Create a new folder. |
rename_mailbox(old_name, new_name) |
WRITE | Rename a folder. |
subscribe_mailbox(name) |
WRITE | Server-side subscription bookmark. |
unsubscribe_mailbox(name) |
WRITE | Remove 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 stable 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
25. The UID is intentionally exposed in summary mode because every
follow-up tool needs it.
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 fromsearch_messages — the UID is extracted automatically.
#Destructive tools
delete_message, expunge, and delete_mailbox are taggeddestructive=True and remove messages or folders server-side without
recovery. Filter them out with exclude_tags=["destructive"] for
triage-only agents.
#Filtering at registration
# Read-only IMAP for a summarization agent.agent.add_toolset(IMAPToolSet(...), include_tags=["read"]) # Read + write but never destructive.agent.add_toolset(IMAPToolSet(...), exclude_tags=["destructive"])#SMTPToolSet
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(model="auto")register_connector(agent, sender)#Tools
| Tool | Permission | Description |
|---|---|---|
validate_connection() |
READ | EHLO/login/NOOP/quit; returns server greeting summary. |
send_email(to, subject, body_text, body_html, cc, bcc, attachments, sender, reply_to) |
WRITE | Build a MIME message and send via send_message. |
send_raw_email(raw_message, recipients, sender) |
WRITE | Send a pre-built MIME via sendmail. |
send_email returns:
{ "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 andsend_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 onlyvalidate_connection.
#Configuration
| Argument | Default | Purpose |
|---|---|---|
host |
required (default factory) | SMTP server hostname. |
port |
465 |
TCP port. Use 587 for STARTTLS. |
use_ssl / starttls |
True / False |
Mutually exclusive transport flags. |
username / password |
"" |
Auth credentials. Skip auth by leaving empty. |
sender |
required | Default From address. |
client_factory |
None | Override the SMTP client (used in tests). |