Inbox Triage
ivy.agent.inbox-triage · v1.1.0
ivyx
Reads the unread mail of an inbox, has a model sort each message into urgent, finance, newsletter, personal or other with a one-line note, and writes the result as a table.
Steps
In the order the agent's file lists them, each with where its inputs come from and the values the file fixes. A branch or a loop is a step too.
- 01Ivy NodeRead the unread mailivy.node.mail-fetch
Reads the newest messages of a mailbox folder that match a query over IMAP and returns each one's sender, subject, date and plain text, newest first.
- takes
usernamefrom the agent's inputqueryfrom the agent's inputlimitfrom the agent's inputimap_hostfrom the agent's inputimap_portfrom the agent's inputuse_sslfrom the agent's input- set
imap_hostimap.gmail.comimap_port993use_ssltruequeryunseenlimit20- secret
mail_passwordthe stored secret mail-password
- 02Model turnSort each message
Sort every message in the context into one category: urgent (needs action today: an outage, a deadline, a request from a customer that cannot wait), finance (an invoice, a receipt, a payment, a bill), newsletter (a bulk mailing or announcement), personal (a note from a person about something that is not work), or other. Return one row per message with uid (copied exactly from the message), from, subject (copied), category and note (one short sentence saying what the message asks or tells). Use only what the messages say; do not leave any message out.
- takes
contextfrom step 1,messages
- 03Ivy NodeCheck each rowivy.node.rows-validate
Checks every row against a JSON Schema and splits the rows into valid ones and invalid ones with their problems.
- takes
rowsfrom step 2,output.rows- set
schema{"type":"object","required":["uid","subject","category","note"],"properties":{"uid":{"type":"string","minLength":1},"...- returns
rows,rejected
- 04Ivy NodeLay out the tableivy.node.markdown-table
Formats rows as a Markdown table, with the columns in the order given or as they first appear.
- takes
rowsfrom step 3,valid_rows- set
columns["category","from","subject","note"]- returns
count,markdown
- 05Ivy NodeWrite the tableivy.node.file-write
Writes text to a file, creating its folder, and returns the absolute path and the number of bytes written.
- takes
textfrom step 4,markdown- set
pathreports/inbox-triage.mdoverwritetrue- returns
path
Nodes it brings
Adding this agent in IVYX Studio adds these nodes with it. A node your workspace already has is kept as it is, even at another version.
What it touches
Collected from what each of its nodes declares, plus the model call when a step is a model turn. A declaration is the author's statement, and it is what policy rules select on.
What it needs
- A stored secret named
mail-password. The agent's file carries the name, never the value.
Inputs
| Field | Type | Description |
|---|---|---|
| usernamerequired | string | The mailbox address, which is the login name; its password is the stored secret mail-password. |
| query | string | Which mail to read: unseen, since:<days>, from:<name>, subject:<word>, joined with spaces. |
| limit | integer | At most this many messages, the newest. |
| imap_host | string | The IMAP server; imap.gmail.com when left out. |
| imap_port | integer | The IMAP port; 993 when left out. |
| use_ssl | boolean | Connect to IMAP over SSL; on when left out. |
Outputs
| Field | Type | Description |
|---|---|---|
| rows | array | One row per message: uid, from, subject, category and note. |
| count | integer | How many messages were sorted. |
| rejected | integer | How many of the model's rows lacked a field. |
| markdown | string | The table that was written. |
| path | string | The file the table was written to. |
Tests
2 of 2 test cases passed on Oct 7, 2026, in the publisher's own environment, before this version was published. The registry keeps that record; it does not run the cases again.
Requires: python:3.9, a mailbox over IMAP (Gmail unless imap_host says otherwise) with its password stored as the secret mail-password, for the cases, GreenMail on 127.0.0.1:3143/3025 (mkdata-mail.py), a model core, the eight [ivyx-test] messages of mkdata-mail.py, unread
- eight-unread
Eight unread test messages: the outage and the full disk are urgent, the three receipts are finance.
- given
imap_host127.0.0.1imap_port3143use_sslfalseusernameagent@ivyx.testqueryunseen subject:[ivyx-test]limit20- expects
countequals 8rejectedequals 0markdownmatches urgent[^\n]*(checkout|disk)[\s\S]*urgent[^\n]*(checkout|disk)pathmatches reports/inbox-triage\.md$
- receipts-only
A narrower query sorts only the mail it names.
- given
imap_host127.0.0.1imap_port3143use_sslfalseusernameagent@ivyx.testqueryunseen subject:[ivyx-test] subject:receiptlimit20- expects
countequals 2markdownmatches finance[\s\S]*finance