Reply to emails with an LLM
This is the canonical trigger workflow, built end to end: someone emails an address, a model reads the message and the documents attached to it, and the result goes back to whoever sent it. Four nodes, no code, no input parameters.
Input (no parameters) → LLM "extract" → Tool "send_email" → Output ↑ reads {{ trigger.* }} — the email itselfBuild it
Section titled “Build it”-
New workflow. Call it something like
Email → Extract → Reply. Drag on an Input node and leave its parameter list empty — this workflow takes nothing from the Run dialog. -
Add an LLM node (id
extract). Pick a capable text model. Leave max tokens and temperature at their defaults. Set the system prompt to:You extract structured, factual information from business email. Never invent values.and the prompt to:
You are an assistant that reads an inbound email (and its attachments) and extracts what matters.From: {{ trigger.from_name }} <{{ trigger.from }}>Subject: {{ trigger.subject }}Date: {{ trigger.date }}--- EMAIL BODY ---{{ trigger.text }}{% if trigger.attachments %}--- ATTACHMENTS ---{% for a in trigger.attachments %}### {{ a.filename }} ({{ a.content_type }}, {{ a.size }} bytes){% if a.text %}{{ a.text }}{% else %}(no extractable text — image or binary){% endif %}{% endfor %}{% endif %}--- TASK ---Reply in Markdown with exactly these sections:1. **Summary** — 2–3 sentences on what this email is about.2. **Key facts** — bullets: people, organisations, amounts, dates, reference numbers, deadlines. Quote exact values.3. **Action items** — who needs to do what (or "None").4. **Attachments** — one line per attachment saying what it contains (or "None").Be concrete. If something is missing or unclear, say so. No greeting, no sign-off.The
{% for %}loop is plain Jinja — every attachment’s extracted text lands in the prompt, so an emailed PDF invoice is read by the model without any parsing code. -
Add a Tool node (id
reply) with server__builtin__and toolsend_email. Arguments:{"subject": "Re: {{ trigger.subject }}","body": "{{ extract.text }}\n\n---\n_Extracted automatically by Catalyst._","format": "markdown"}tois the sender. Theifis a small courtesy for the Send test button: its synthetic event comes from[email protected], and an emptytomakessend_emailfall back to your own address — so a test reply lands in your inbox instead of bouncing off a fake one. -
Add an Output node so the run records what happened:
from → {{ trigger.from }}subject → {{ trigger.subject }}attachments → {{ trigger.attachments | length }}extracted → {{ extract.text }}email_result → {{ reply.result }} -
Wire Input → extract → reply → Output and Save.
Give it an inbox
Section titled “Give it an inbox”-
Click Triggers in the toolbar → New Trigger → Email.
-
Optionally restrict Allowed senders (e.g.
@yourcompany.com). Leave Extract text from attachments on. Create. -
Copy the address from the row — it looks like
[email protected].
Test it
Section titled “Test it”- Send test on the trigger row fires a fake email through the real path. Within a few
seconds you’ll see an accepted line under Deliveries, a run in History with an
Email badge, and — because of the
tofallback above — a reply in your own inbox. - Then do it for real: email the address from your mail client with a PDF attached. The
reply comes back to the address you sent from, and the run’s
triggeroutput shows the attachment with its extractedtext.
Variations
Section titled “Variations”- Spreadsheets, properly. The model sees a capped text preview of an emailed
.xlsx; for totals or joins add a Python node — the file is already at./inputs/attachments/, sopd.read_excel(glob.glob('./inputs/attachments/*.xlsx')[0])gives you real numbers. - Route instead of reply. Add a Condition on
{{ trigger.subject }}or on something the LLM extracted, and send different mail for invoices vs. everything else. - Look at the picture. For emailed screenshots, put
{{ trigger.attachments.0.url }}in a vision model’s Images field — the model sees the attachment instead of reading text. - Feed a parameterised workflow. If the workflow already has inputs, keep them and map
{{ trigger.subject }}/{{ trigger.attachments.0.url }}onto them in the trigger’s Map from event box; the same workflow then runs from a schedule, the Run dialog, or mail.