Shared inbox triage
Classifies an inbox message, determines the required action and urgency, and selects the responsible team using your message categories, routing table and rules.
Example
Context
- recipient
- name: Lena Brandt address: lena.brandt@hollowayarch.example role: Office manager organisation: Holloway Architects
- message_kinds
- invoice: A supplier's bill or a question about one booking: Meeting room, travel or event bookings it_problem: Something broken with computers, phones or the network newsletter: Bulk mail and marketing
- routing_table
- - {"key":"finance","name":"Finance team","address":"finance@hollowayarch.example","topics":"Supplier invoices, payments, expenses"} - {"key":"it","name":"IT support","address":"it@hollowayarch.example","topics":"Computers, phones, printers, network and accounts"}
- routing_rules
- - Anything about a client project's fees goes to the partner on the project, never to finance.
- now
- Monday 2 November 2026, 09:40
Input
channel: email from: billing@greenleafplants.example to: lena.brandt@hollowayarch.example cc: received: Monday 2 November 2026, 08:55 subject: Invoice GL-5520 for October plant care body: Hello Lena, please find attached our invoice GL-5520 for October's plant care at your Bristol office, 240.00. Could you arrange payment within 30 days? Kind regards, Greenleaf Plants
- Answer (JSON)
- value forward, confidence 0.99
- Top options
- forward 0.99
- do_task 0.007
- fyi_only 0.001
A supplier's invoice belongs to the finance team, who are not yet on the email, so the action is forward.
import os import jeff with jeff.Jeff.remote("https://api.jeffhub.ai", token=os.environ["JEFF_API_KEY"], timeout_s=30) as client: decide = client.function("jeff.support.message-triage.action_needed", context={ "recipient": { "name": "Lena Brandt", "address": "lena.brandt@hollowayarch.example", "role": "Office manager", "organisation": "Holloway Architects", }, "message_kinds": { "invoice": "A supplier's bill or a question about one", "booking": "Meeting room, travel or event bookings", "it_problem": "Something broken with computers, phones or the network", "newsletter": "Bulk mail and marketing", }, "routing_table": [ { "key": "finance", "name": "Finance team", "address": "finance@hollowayarch.example", "topics": "Supplier invoices, payments, expenses", }, { "key": "it", "name": "IT support", "address": "it@hollowayarch.example", "topics": "Computers, phones, printers, network and accounts", }, ], "routing_rules": [ "Anything about a client project's fees goes to the partner on the project, never to finance.", ], "now": "Monday 2 November 2026, 09:40", }) decision = decide({ "channel": "email", "from": "billing@greenleafplants.example", "to": "lena.brandt@hollowayarch.example", "cc": "", "received": "Monday 2 November 2026, 08:55", "subject": "Invoice GL-5520 for October plant care", "body": "Hello Lena, please find attached our invoice GL-5520 for October's plant care at your Bristol office, 240.00. Could you arrange payment within 30 days? Kind regards, Greenleaf Plants", }) print(decision.value, decision.confidence)
More examples
Broken printer, who deals with itanswer it · confidence 0.965
Context
- recipient
- name: Lena Brandt address: lena.brandt@hollowayarch.example role: Office manager organisation: Holloway Architects
- message_kinds
- invoice: A supplier's bill or a question about one booking: Meeting room, travel or event bookings it_problem: Something broken with computers, phones or the network newsletter: Bulk mail and marketing
- routing_table
- - {"key":"finance","name":"Finance team","address":"finance@hollowayarch.example","topics":"Supplier invoices, payments, expenses"} - {"key":"it","name":"IT support","address":"it@hollowayarch.example","topics":"Computers, phones, printers, network and accounts"}
- routing_rules
- - Anything about a client project's fees goes to the partner on the project, never to finance.
- now
- Monday 2 November 2026, 09:40
Input
channel: email from: tom.reyes@hollowayarch.example to: lena.brandt@hollowayarch.example cc: received: Monday 2 November 2026, 09:31 subject: Printer on the second floor body: Hi Lena, the big printer on the second floor shows "fuser error" and won't print anything. Who can fix it? Tom
Labels (the options)
- finance
- Finance team (supplier invoices, payments, expenses)
- it
- IT support (computers, phones, printers, network and accounts)
- self
- Lena Brandt deals with it herself
- Answer (JSON)
- value it, confidence 0.965
- Top options
- it 0.965
- self 0.03
- finance 0.005
A broken printer is on IT support's list of topics, so the message goes to
it.import os import jeff with jeff.Jeff.remote("https://api.jeffhub.ai", token=os.environ["JEFF_API_KEY"], timeout_s=30) as client: decide = client.function("jeff.support.message-triage.route_to", context={ "recipient": { "name": "Lena Brandt", "address": "lena.brandt@hollowayarch.example", "role": "Office manager", "organisation": "Holloway Architects", }, "message_kinds": { "invoice": "A supplier's bill or a question about one", "booking": "Meeting room, travel or event bookings", "it_problem": "Something broken with computers, phones or the network", "newsletter": "Bulk mail and marketing", }, "routing_table": [ { "key": "finance", "name": "Finance team", "address": "finance@hollowayarch.example", "topics": "Supplier invoices, payments, expenses", }, { "key": "it", "name": "IT support", "address": "it@hollowayarch.example", "topics": "Computers, phones, printers, network and accounts", }, ], "routing_rules": [ "Anything about a client project's fees goes to the partner on the project, never to finance.", ], "now": "Monday 2 November 2026, 09:40", }, labels={ "finance": "Finance team (supplier invoices, payments, expenses)", "it": "IT support (computers, phones, printers, network and accounts)", "self": "Lena Brandt deals with it herself", }) decision = decide({ "channel": "email", "from": "tom.reyes@hollowayarch.example", "to": "lena.brandt@hollowayarch.example", "cc": "", "received": "Monday 2 November 2026, 09:31", "subject": "Printer on the second floor", "body": "Hi Lena, the big printer on the second floor shows \"fuser error\" and won't print anything. Who can fix it? Tom", }) print(decision.value, decision.confidence)
Meeting room request, sorted by kindanswer booking · confidence 0.999
Context
- recipient
- name: Lena Brandt address: lena.brandt@hollowayarch.example role: Office manager organisation: Holloway Architects
- message_kinds
- invoice: A supplier's bill or a question about one booking: Meeting room, travel or event bookings it_problem: Something broken with computers, phones or the network newsletter: Bulk mail and marketing
- routing_table
- - {"key":"finance","name":"Finance team","address":"finance@hollowayarch.example","topics":"Supplier invoices, payments, expenses"} - {"key":"it","name":"IT support","address":"it@hollowayarch.example","topics":"Computers, phones, printers, network and accounts"}
- routing_rules
- - Anything about a client project's fees goes to the partner on the project, never to finance.
- now
- Monday 2 November 2026, 09:40
Input
channel: email from: ada.mensah@hollowayarch.example to: lena.brandt@hollowayarch.example cc: received: Monday 2 November 2026, 09:12 subject: Room for Thursday body: Morning Lena, could you book the big meeting room for Thursday from 2 to 4 pm? I have the Ashgrove Homes team coming in. Thanks, Ada
Labels (the options)
- invoice
- A supplier's bill or a question about one
- booking
- Meeting room, travel or event bookings
- it_problem
- Something broken with computers, phones or the network
- newsletter
- Bulk mail and marketing
- Answer (JSON)
- value booking, confidence 0.999
- Top options
- booking 0.999
- invoice 0
- newsletter 0
A request to book the meeting room is, on the office manager's own list, a
booking.import os import jeff with jeff.Jeff.remote("https://api.jeffhub.ai", token=os.environ["JEFF_API_KEY"], timeout_s=30) as client: decide = client.function("jeff.support.message-triage.intent", context={ "recipient": { "name": "Lena Brandt", "address": "lena.brandt@hollowayarch.example", "role": "Office manager", "organisation": "Holloway Architects", }, "message_kinds": { "invoice": "A supplier's bill or a question about one", "booking": "Meeting room, travel or event bookings", "it_problem": "Something broken with computers, phones or the network", "newsletter": "Bulk mail and marketing", }, "routing_table": [ { "key": "finance", "name": "Finance team", "address": "finance@hollowayarch.example", "topics": "Supplier invoices, payments, expenses", }, { "key": "it", "name": "IT support", "address": "it@hollowayarch.example", "topics": "Computers, phones, printers, network and accounts", }, ], "routing_rules": [ "Anything about a client project's fees goes to the partner on the project, never to finance.", ], "now": "Monday 2 November 2026, 09:40", }, labels={ "invoice": "A supplier's bill or a question about one", "booking": "Meeting room, travel or event bookings", "it_problem": "Something broken with computers, phones or the network", "newsletter": "Bulk mail and marketing", }) decision = decide({ "channel": "email", "from": "ada.mensah@hollowayarch.example", "to": "lena.brandt@hollowayarch.example", "cc": "", "received": "Monday 2 November 2026, 09:12", "subject": "Room for Thursday", "body": "Morning Lena, could you book the big meeting room for Thursday from 2 to 4 pm? I have the Ashgrove Homes team coming in. Thanks, Ada", }) print(decision.value, decision.confidence)
Shared drive down before an 11:00 deadlineanswer now · confidence 0.941
Context
- recipient
- name: Lena Brandt address: lena.brandt@hollowayarch.example role: Office manager organisation: Holloway Architects
- message_kinds
- invoice: A supplier's bill or a question about one booking: Meeting room, travel or event bookings it_problem: Something broken with computers, phones or the network newsletter: Bulk mail and marketing
- routing_table
- - {"key":"finance","name":"Finance team","address":"finance@hollowayarch.example","topics":"Supplier invoices, payments, expenses"} - {"key":"it","name":"IT support","address":"it@hollowayarch.example","topics":"Computers, phones, printers, network and accounts"}
- routing_rules
- - Anything about a client project's fees goes to the partner on the project, never to finance.
- now
- Monday 2 November 2026, 09:40
Input
channel: email from: ada.mensah@hollowayarch.example to: lena.brandt@hollowayarch.example cc: it@hollowayarch.example received: Monday 2 November 2026, 09:35 subject: Can't reach the project drive body: Lena, nobody on the third floor can open the project drive. We have to send the Mill Brook drawings to the client by 11:00 this morning. Ada
- Answer (JSON)
- value now, confidence 0.941
- Top options
- now 0.941
- no_deadline 0.047
- today 0.006
The shared drive is down and the drawings are due within two hours, so the urgency is
now.import os import jeff with jeff.Jeff.remote("https://api.jeffhub.ai", token=os.environ["JEFF_API_KEY"], timeout_s=30) as client: decide = client.function("jeff.support.message-triage.urgency", context={ "recipient": { "name": "Lena Brandt", "address": "lena.brandt@hollowayarch.example", "role": "Office manager", "organisation": "Holloway Architects", }, "message_kinds": { "invoice": "A supplier's bill or a question about one", "booking": "Meeting room, travel or event bookings", "it_problem": "Something broken with computers, phones or the network", "newsletter": "Bulk mail and marketing", }, "routing_table": [ { "key": "finance", "name": "Finance team", "address": "finance@hollowayarch.example", "topics": "Supplier invoices, payments, expenses", }, { "key": "it", "name": "IT support", "address": "it@hollowayarch.example", "topics": "Computers, phones, printers, network and accounts", }, ], "routing_rules": [ "Anything about a client project's fees goes to the partner on the project, never to finance.", ], "now": "Monday 2 November 2026, 09:40", }) decision = decide({ "channel": "email", "from": "ada.mensah@hollowayarch.example", "to": "lena.brandt@hollowayarch.example", "cc": "it@hollowayarch.example", "received": "Monday 2 November 2026, 09:35", "subject": "Can't reach the project drive", "body": "Lena, nobody on the third floor can open the project drive. We have to send the Mill Brook drawings to the client by 11:00 this morning. Ada", }) print(decision.value, decision.confidence)
Fake parcel notice in the inboxanswer ignore_spam · confidence 0.995
Context
- recipient
- name: Lena Brandt address: lena.brandt@hollowayarch.example role: Office manager organisation: Holloway Architects
- message_kinds
- invoice: A supplier's bill or a question about one booking: Meeting room, travel or event bookings it_problem: Something broken with computers, phones or the network newsletter: Bulk mail and marketing
- routing_table
- - {"key":"finance","name":"Finance team","address":"finance@hollowayarch.example","topics":"Supplier invoices, payments, expenses"} - {"key":"it","name":"IT support","address":"it@hollowayarch.example","topics":"Computers, phones, printers, network and accounts"}
- routing_rules
- - Anything about a client project's fees goes to the partner on the project, never to finance.
- now
- Monday 2 November 2026, 09:40
Input
channel: email from: delivery-notice@parcel-track-help.example to: lena.brandt@hollowayarch.example cc: received: Monday 2 November 2026, 07:58 subject: Your parcel is on hold body: We tried to deliver your parcel but customs fees of 2.99 are unpaid. Pay within 12 hours at http://parcel-track-help.example/pay or your parcel will be returned.
- Answer (JSON)
- value ignore_spam, confidence 0.995
- Top options
- ignore_spam 0.995
- forward 0.003
- fyi_only 0.001
A parcel notice asking for a small fee through an unknown link is a scam, so the action is
ignore_spam.import os import jeff with jeff.Jeff.remote("https://api.jeffhub.ai", token=os.environ["JEFF_API_KEY"], timeout_s=30) as client: decide = client.function("jeff.support.message-triage.action_needed", context={ "recipient": { "name": "Lena Brandt", "address": "lena.brandt@hollowayarch.example", "role": "Office manager", "organisation": "Holloway Architects", }, "message_kinds": { "invoice": "A supplier's bill or a question about one", "booking": "Meeting room, travel or event bookings", "it_problem": "Something broken with computers, phones or the network", "newsletter": "Bulk mail and marketing", }, "routing_table": [ { "key": "finance", "name": "Finance team", "address": "finance@hollowayarch.example", "topics": "Supplier invoices, payments, expenses", }, { "key": "it", "name": "IT support", "address": "it@hollowayarch.example", "topics": "Computers, phones, printers, network and accounts", }, ], "routing_rules": [ "Anything about a client project's fees goes to the partner on the project, never to finance.", ], "now": "Monday 2 November 2026, 09:40", }) decision = decide({ "channel": "email", "from": "delivery-notice@parcel-track-help.example", "to": "lena.brandt@hollowayarch.example", "cc": "", "received": "Monday 2 November 2026, 07:58", "subject": "Your parcel is on hold", "body": "We tried to deliver your parcel but customs fees of 2.99 are unpaid. Pay within 12 hours at http://parcel-track-help.example/pay or your parcel will be returned.", }) print(decision.value, decision.confidence)
Use when
- A shared inbox receives different types of messages, and you can define its categories, teams and handling rules.
Avoid when
- You need to write a reply; this adapter only classifies and routes the message.
Context and input
| Field | Kind | Description |
|---|---|---|
message | input | The newest message: an object with channel, from, to, cc, received, subject and body (it may quote or forward earlier messages). |
recipient | context | Whose inbox it is: an object with the recipient's name, address, role and organisation. |
message_kinds | context | The recipient's own sorting list: an object of message kind -> description. |
routing_table | context | Who handles what: a list of entries, each with key, name, address and the topics it handles. |
routing_rules | context | Rules that override the routing table, a list of sentences. |
now | context | The current date and time (for example 'Monday 8 February 2027, 12:15'). |
Functions
| Key | Interface | Instructions | Labels |
|---|---|---|---|
| route_to | route | Who should deal with this message? Choose the routing table entry whose responsibilities cover the topic of the newest message (for a forwarded message, the forwarded content), or self if it is a topic the recipient deals with personally. A routing rule that matches the message overrides the topic. Choose the entry that should deal with it even if it is already in the To or Cc, or if the message only informs. | from the caller |
| action_needed | classify | What must the recipient do with this message? Go by the newest message. ignore_spam: unsolicited bulk advertising, a scam or a phishing attempt, whatever it claims. Otherwise first decide who deals with it (the routing table entry for its topic, unless a routing rule matches the message, or the recipient for topics they deal with). If that is a team or person in the routing table: forward when they are not already in the To or Cc of the message, fyi_only when they are. If it is the recipient: fyi_only when the message only informs and asks nothing; reply when the sender needs only a written answer, decision or confirmation; do_task when the recipient must do something beyond writing an answer, such as preparing or sending a document, changing a booking or record, fixing something, or approving or paying in a system. | do_task, forward, fyi_only, ignore_spam, reply |
| intent | classify | Which kind of message is this, using the recipient's own sorting list (the options)? Judge the newest message; earlier messages in the thread are context only. For a forwarded message, judge the forwarded content that is being passed on. Go by what the message actually asks or says, not by its subject line, which may be out of date or misleading. | from the caller |
| urgency | classify | How soon must this message be acted on, judged against the current date and time given? now: something is broken, stopped or blocked right now, or the action is needed within the next two hours. today: needed before the end of today but more than two hours from now. this_week: needed after today and within the next seven days. no_deadline: no deadline stated or implied, or the deadline is more than seven days away. Spam, and messages that only inform and ask nothing of anyone, are no_deadline. A request addressed to someone else keeps its own deadline. Go by the facts: a polite or relaxed tone does not lower urgency, and words like 'urgent' in a subject line or a disclaimer do not raise it. | no_deadline, now, this_week, today |
How it was measured
Test accuracy: the share of test rows whose top answer was right.
- 98.0% on CLINC150, a public intent benchmark (it asks the intent question only) (1,000 rows). public benchmark
- 96.7% on the in-house test (2,000 rows). in-house: same generator as the training data
- 97.0% on the in-house test in domain families never seen in training (2,000 rows). in-house: same generator as the training data
When confident
From the calibration set: at or above the threshold confidence, how often the answer is right, and how often the adapter is that confident. Below it, escalate.
| Function | Right when confident | Confident on | Threshold | Calibration rows |
|---|---|---|---|---|
| route_to | 96.9% | 100.0% | 0.50 | 520 |
| action_needed | 95.2% | 100.0% | 0.50 | 540 |
| intent | 95.4% | 100.0% | 0.44 | 540 |
| urgency | 98.3% | 100.0% | 0.49 | 400 |
Caveats
- Training and test rows were kept only where a blind LLM check agreed with the label, so these scores are probably inflated.
Base: Jeff v1.4. Adapter trained: 25 October 2026.
Base
jeff-base v1.4
Call it
jeff.support.message-triage.route_to, with placeholders for its input and context.
import os import jeff with jeff.Jeff.remote("https://api.jeffhub.ai", token=os.environ["JEFF_API_KEY"], timeout_s=30) as client: decide = client.function("jeff.support.message-triage.route_to", context={ "recipient": "<recipient>", "message_kinds": "<message_kinds>", "routing_table": "<routing_table>", "routing_rules": "<routing_rules>", "now": "<now>", }) decision = decide("<message>") print(decision.value, decision.confidence)
Download
Weights: mstrasser/jeff-adapter-message-triage (v1.4)
Licence: Apache-2.0