atteq API
  • Guides
  • Recipes
  • Cookbook
  • API Reference
Mail triageMemoryTriageWeekly summary
powered by Zudoku
Cookbook

Mail triage

Decide what each mail on one connection calls for -- answer, draft, internal only, escalate or ignore -- with a small model, and wake your agent only for the mail that needs it.

This page is made from the recipe mail_triage, version 1, and says nothing the recipe does not. How a recipe is composed explains every word it uses.

Its setup

Each mail on [The email connection Mail triage answers] is read once: [The agent that answers and drafts] answers or drafts, [The person escalations go to] gets escalations, and [Label for what is answered], [Label for what is drafted], [Label for what is internal only], [Label for what is escalated] and [Label for what is ignored] mark outcomes.

Each phrase in brackets is a parameter the recipe asks for, named as the table of parameters below names it.

Its parts, in the order data travels

  1. thread_flow answers — Makes this triage the flow the connection answers each mail with.
    • answers the connection ${param.connection} (the value of the parameter connection) with the flow triage, in place of the flow it answered with
  2. flow triage — Decides what each mail calls for and does it, by the outcome the reading gives.
    • step read runs the tool classify
    • step label_answer runs the tool put_label, only while label_answer has a value
    • step answered runs the tool answer
    • step label_draft runs the tool put_label, only while label_draft has a value
    • step drafted runs the tool draft
    • step label_internal runs the tool put_label, only while label_internal has a value
    • step kept_inside runs the tool declare
    • step label_escalate runs the tool put_label, only while label_escalate has a value
    • step escalated runs the tool to_people
    • step label_ignore runs the tool put_label, only while label_ignore has a value
    • step ignored runs the tool declare
    • step unclear runs the tool declare
    • step unclear_to_people runs the tool to_people
  3. tool classify — Asks once which of the five outcomes the mail calls for, and why.
    • runs the built-in structured_answers
  4. tool put_label — Puts the label chosen for the outcome on the conversation.
    • runs the built-in label_conversation
  5. tool declare — Notes the outcome on the decision record where there is nothing more to do.
    • runs the built-in pass_value
  6. tool to_people — Leaves the thread with the person you chose, or waiting for a person, saying why.
    • runs the built-in hand_to_person
  7. tool answer — Hands the mail to the agent you chose, which replies as the connection's mode allows.
    • runs the built-in hand_to_agent
  8. tool draft — Hands the mail to the agent you chose, which prepares the reply for a person to send.
    • runs the built-in hand_to_agent

Its parameters

ParameterKindWhat it asksValue
connectionconnectionThe email connection Mail triage answersrequired; fixed once installed
agentagentThe agent that answers and drafts — It reads only the mail that is answered or drafted.required; fixed once installed
personpersonThe person escalations go to — Leave it blank and an escalated thread waits for a person.may be left blank
modeltextThe model that reads each mail — A model the platform offers. Blank, the platform's own reading model reads.default ""
line_answertextWhat is answered — One line; it is all the model reads of this outcome.default Routine mail the agent can answer on its own: a question its knowledge covers, a confirmation or a simple request.
line_drafttextWhat is drafted for review — One line; it is all the model reads of this outcome.default Mail the agent can answer but whose reply a person should read first: a commitment, a price, a complaint or anything delicate.
line_internaltextWhat is dealt with without replying — One line; it is all the model reads of this outcome.default Mail that calls for something on our side but no reply to the sender: a notice to file, a receipt, an alert.
line_escalatetextWhat goes to a person — One line; it is all the model reads of this outcome.default Mail only a person can handle: a legal threat, a large amount or anything the agent should not decide.
line_ignoretextWhat is ignored — One line; it is all the model reads of this outcome.default Mail nobody needs to act on: newsletters, advertising or automatic notices with nothing to do.
label_answerlabelsLabel for what is answered — Put on the conversation after the decision; blank, none.may be left blank
label_draftlabelsLabel for what is drafted — Put on the conversation after the decision; blank, none.may be left blank
label_internallabelsLabel for what is internal only — Put on the conversation after the decision; blank, none.may be left blank
label_escalatelabelsLabel for what is escalated — Put on the conversation after the decision; blank, none.may be left blank
label_ignorelabelsLabel for what is ignored — Put on the conversation after the decision; blank, none.may be left blank

How its data moves

The flow triage:

StepInputValueComes from
readtextTo: $ref:trigger.mail.to Cc: $ref:trigger.mail.cc $ref:trigger.textmail.to of what set the run off; mail.cc of what set the run off; text of what set the run off
label_answeradd${param.label_answer}replace with the value of the parameter label_answer
answeredtext$ref:trigger.texttext of what set the run off
answeredmessages$ref:trigger.messagesmessages of what set the run off
label_draftadd${param.label_draft}replace with the value of the parameter label_draft
draftedtext$ref:trigger.texttext of what set the run off
draftedmessages$ref:trigger.messagesmessages of what set the run off
label_internaladd${param.label_internal}replace with the value of the parameter label_internal
kept_insidevalue.outcomeinternal_onlyfixed
label_escalateadd${param.label_escalate}replace with the value of the parameter label_escalate
escalatedreasonMail triage: $ref:read.reasonreason of the step read's answer
label_ignoreadd${param.label_ignore}replace with the value of the parameter label_ignore
ignoredvalue.outcomeignorefixed
unclearvalue.outcomeescalatefixed
unclear_to_peoplereasonMail triage could not tell what this mail calls for, so a person looks at it.fixed

A value written $ref:<step>.<path> is what an earlier step answered, and $ref:trigger.<path> is what set the run off. A value with ${...} in it is a placeholder: building it by hand, replace it with your own.

Build it yourself

To install it as it is: POST /api/v1/recipes/{key}/install with the key mail_triage and a value for each parameter. To build the same thing with your own particulars, make each part in this order -- each one may name the ones before it -- with the operations below.

The YAML under each part is the recipe's declaration, not a request body: the API takes other names for the same things (a tool's builtin is source and config.builtin, a step's tool is tool_id, an edge's from is source_step_id). From a recipe to the API on the lead page maps every field, kind by kind.

1. tool classify

Asks once which of the five outcomes the mail calls for, and why.

  • POST /api/v1/tools creates the instance: a built-in's, or a flow offered as one tool by its config.flow_id.
Code
kind: tool key: classify name: Mail triage on ${param.connection.name} — what the mail calls for says: Asks once which of the five outcomes the mail calls for, and why. builtin: structured_answers config: model: ${param.model} fields: - key: outcome type: option options: - answer - draft - internal_only - escalate - ignore description: 'What this mail calls for, judging by its text, its subject, and whom it was addressed and copied to. Answer with exactly one of these: - answer: ${param.line_answer} - draft: ${param.line_draft} - internal_only: ${param.line_internal} - escalate: ${param.line_escalate} - ignore: ${param.line_ignore}' - key: reason type: string description: In one short sentence, why this mail calls for that outcome.

2. tool put_label

Puts the label chosen for the outcome on the conversation.

  • POST /api/v1/tools creates the instance: a built-in's, or a flow offered as one tool by its config.flow_id.
Code
kind: tool key: put_label name: Mail triage on ${param.connection.name} — put the label on says: Puts the label chosen for the outcome on the conversation. builtin: label_conversation

3. tool declare

Notes the outcome on the decision record where there is nothing more to do.

  • POST /api/v1/tools creates the instance: a built-in's, or a flow offered as one tool by its config.flow_id.
Code
kind: tool key: declare name: Mail triage on ${param.connection.name} — note the outcome says: Notes the outcome on the decision record where there is nothing more to do. description: The step that ends a branch with nothing to do, so the flow's record carries the outcome a person fixed on it. It sends and hands over nothing. builtin: pass_value

4. tool to_people

Leaves the thread with the person you chose, or waiting for a person, saying why.

  • POST /api/v1/tools creates the instance: a built-in's, or a flow offered as one tool by its config.flow_id.
Code
kind: tool key: to_people name: Mail triage on ${param.connection.name} — to a person says: Leaves the thread with the person you chose, or waiting for a person, saying why. builtin: hand_to_person config: person_id: ${param.person}

5. tool answer

Hands the mail to the agent you chose, which replies as the connection's mode allows.

  • POST /api/v1/tools creates the instance: a built-in's, or a flow offered as one tool by its config.flow_id.
Code
kind: tool key: answer name: Mail triage on ${param.connection.name} — answered by ${param.agent.name} says: Hands the mail to the agent you chose, which replies as the connection's mode allows. description: Hands what arrived on ${param.connection.name} to ${param.agent.name}, to reply. builtin: hand_to_agent config: agent_id: ${param.agent} mode: send deadline_seconds: 120

6. tool draft

Hands the mail to the agent you chose, which prepares the reply for a person to send.

  • POST /api/v1/tools creates the instance: a built-in's, or a flow offered as one tool by its config.flow_id.
Code
kind: tool key: draft name: Mail triage on ${param.connection.name} — drafted by ${param.agent.name} says: Hands the mail to the agent you chose, which prepares the reply for a person to send. description: Hands what arrived on ${param.connection.name} to ${param.agent.name}, to prepare a reply. builtin: hand_to_agent config: agent_id: ${param.agent} mode: prepare deadline_seconds: 120

7. flow triage

Decides what each mail calls for and does it, by the outcome the reading gives.

  • POST /api/v1/flows creates the flow.
  • POST /api/v1/flows/{flow_id}/steps adds each step, naming the tool it runs.
  • POST /api/v1/flows/{flow_id}/edges adds each edge.
  • PATCH /api/v1/flows/{flow_id}/entry-step sets the step a run starts at.
Code
kind: flow key: triage name: Mail triage on ${param.connection.name} says: Decides what each mail calls for and does it, by the outcome the reading gives. steps: - key: read tool: ${resource.classify} params: text: 'To: $ref:trigger.mail.to Cc: $ref:trigger.mail.cc $ref:trigger.text' - key: label_answer tool: ${resource.put_label} params: add: ${param.label_answer} when: ${param.label_answer} - key: answered tool: ${resource.answer} params: text: $ref:trigger.text messages: $ref:trigger.messages - key: label_draft tool: ${resource.put_label} params: add: ${param.label_draft} when: ${param.label_draft} - key: drafted tool: ${resource.draft} params: text: $ref:trigger.text messages: $ref:trigger.messages - key: label_internal tool: ${resource.put_label} params: add: ${param.label_internal} when: ${param.label_internal} - key: kept_inside tool: ${resource.declare} params: value: outcome: internal_only - key: label_escalate tool: ${resource.put_label} params: add: ${param.label_escalate} when: ${param.label_escalate} - key: escalated tool: ${resource.to_people} params: reason: 'Mail triage: $ref:read.reason' - key: label_ignore tool: ${resource.put_label} params: add: ${param.label_ignore} when: ${param.label_ignore} - key: ignored tool: ${resource.declare} params: value: outcome: ignore - key: unclear tool: ${resource.declare} params: value: outcome: escalate - key: unclear_to_people tool: ${resource.to_people} params: reason: Mail triage could not tell what this mail calls for, so a person looks at it. edges: - from: read to: label_answer condition: field: read.outcome op: === value: answer - from: read to: answered condition: field: read.outcome op: === value: answer - from: read to: label_draft condition: field: read.outcome op: === value: draft - from: read to: drafted condition: field: read.outcome op: === value: draft - from: read to: label_internal condition: field: read.outcome op: === value: internal_only - from: read to: kept_inside condition: field: read.outcome op: === value: internal_only - from: read to: label_escalate condition: field: read.outcome op: === value: escalate - from: read to: escalated condition: field: read.outcome op: === value: escalate - from: read to: label_ignore condition: field: read.outcome op: === value: ignore - from: read to: ignored condition: field: read.outcome op: === value: ignore - from: read to: unclear - from: label_answer to: answered - from: label_draft to: drafted - from: label_internal to: kept_inside - from: label_escalate to: escalated - from: label_ignore to: ignored - from: unclear to: unclear_to_people

8. thread_flow answers

Makes this triage the flow the connection answers each mail with.

No public operation places a flow on a connection that already answers. The nearest by hand makes the connection's channel with the flow, which a connection already answered has to be made again for:

  • POST /api/v1/channels makes the connection's channel, answered by the flow it names.
Code
kind: thread_flow key: answers name: Mail triage answers ${param.connection.name} says: Makes this triage the flow the connection answers each mail with. connection: ${param.connection} flow: ${resource.triage}

How to adapt it

Choose the connection, the agent that answers and drafts, the person escalations go to -- or none, and the thread waits for a person -- and, if you want them, a label for each outcome and the smaller model that reads. Tune what goes under each outcome by rewriting its line, which is all the model reads of the recipe. To build your own, keep its shape: a reading step whose only answers are the five outcomes, a branch for each, the agent handed the mail in the mode the outcome calls for, and a label put on only where one was chosen.

What cannot change once installed

Installed, every part is the recipe's: it is listed where its kind lives, marked as the recipe's, and refuses every edit and deletion by hand, naming the recipe.

  • tool classify: its configuration and fixed values.
  • tool put_label: its configuration and fixed values.
  • tool declare: its configuration and fixed values.
  • tool to_people: its configuration and fixed values.
  • tool answer: its configuration and fixed values.
  • tool draft: its configuration and fixed values.
  • flow triage: its steps and edges.
  • thread_flow answers: the connection it answers and the flow it runs there.

What stays the organization's:

  • the values of person, model, line_answer, line_draft, line_internal, line_escalate, line_ignore, label_answer, label_draft, label_internal, label_escalate, label_ignore, changed on the installed recipe and written through to every part that uses them;
  • not connection: the connection is fixed once installed, because it says which install this is -- answering another connection is installing the recipe there;
  • not agent: the agent it names, or naming none, is fixed once installed -- answering the connection with another is uninstalling the recipe and installing it again;
  • whether the recipe is on, which switches its automations on and off together;
  • presentation -- a column's width, a step's position -- which changes nothing it runs.

What it costs

What its parts consume -- a tool execution, a model's tokens, a message, storage -- is recorded and charged as those parts are, exactly as your own use of them is; nothing is charged for the recipe itself.

Last modified on November 4, 2026
Memory
On this page
  • Its setup
  • Its parts, in the order data travels
  • Its parameters
  • How its data moves
  • Build it yourself
    • 1. tool classify
    • 2. tool put_label
    • 3. tool declare
    • 4. tool to_people
    • 5. tool answer
    • 6. tool draft
    • 7. flow triage
    • 8. thread_flow answers
  • How to adapt it
  • What cannot change once installed
  • What it costs
YAML
YAML
YAML
YAML
YAML
YAML
YAML
YAML