n8n workflow · live in production

Every channel drops
into one inbox.
Triage runs itself.

Gmail, Telegram, and WhatsApp messages get read, classified, and routed by an AI agent with memory and a company knowledge base — urgent cases escalate to a CRM ticket and ops alert, standard ones get a drafted reply, spam gets logged and ignored. No human reads every message first.

Architecture

One normalized shape. Every extra piece optional.

Every channel maps to the same fields — from, subject, body, replyTarget — before it ever reaches the agent. Only Gmail + one LLM provider are required to run this; every other integration below is env-gated and skips cleanly when unset.

full pipeline · solid = required, dashed = optional / env-gated swipe to see full diagram → Gmail trigger + attach. · required Telegram bot trigger · optional WhatsApp cloud API · optional merge AI Agent LLM: OpenAI (default) + Gemini/Claude opt-in (off) Memory: per sender Tools: RAG · CRM lookup → structured JSON output Urgent CRM ticket + sheet log Standard reply drafted, same channel Spam logged only Attachments Gmail only, 0–N per message → Drive upload (optional) → own sheet tab, by messageId never blocks the main row Ops alerts LINE · Telegram · WhatsApp each gated on its own env var unset → node never reached fires only on Urgent
normalize / agent urgent → CRM ticket, ops alert optional standard → drafted reply, same channel spam → sheet log only solid border = required dashed border = optional, env-gated
full pipeline · solid = required, dashed = optional / env-gated
Gmail
trigger + attach. · required
Telegram
bot trigger · optional
WhatsApp
cloud API · optional
merge
AI Agent
LLM: OpenAI (default)
+ Gemini/Claude opt-in (off)
Memory: per sender
Tools: RAG · CRM lookup
→ structured JSON output
Urgent
CRM ticket + sheet log
Standard
reply drafted, same channel
Spam
logged only
Attachments
Gmail only, 0–N per message → Drive upload (optional) → own sheet tab, by messageId — never blocks the main row
Ops alerts
LINE · Telegram · WhatsApp, each gated on its own env var — unset → node never reached, fires only on Urgent
normalize / agent urgent → CRM ticket, ops alert optional standard → drafted reply, same channel spam → sheet log only solid border = required dashed border = optional, env-gated
Routing outcomes

Three ways a message can end.

The agent is forced through a structured JSON schema — category, priority, summary, suggested reply, needs_human — so these three branches are deterministic, not vibes-based prose parsing.

Urgent

Escalate, don't auto-reply

CRM ticket created, ops pinged on LINE + Telegram + WhatsApp, logged to sheet. The agent deliberately leaves suggested_reply empty and flags needs_human: true — it won't draft a response to an angry customer or a payment failure.

Standard

Draft, matched to channel

Reply is grounded in the company KB via RAG and the sender's CRM record, then routed back on the same channel it arrived on — short for Telegram/WhatsApp, longer allowed for email.

Spam

Log and move on

No reply, no escalation — just a row in the ops sheet for the record, so nothing silently vanishes even when it's not worth a response.

Design choices

Built to fail gracefully, not just to work once.

Single provider by default, fallback as opt-in

Ships wired to OpenAI (gpt-4o-mini) only — Gemini and Claude nodes are in the graph but shipped disabled, an advanced opt-in for anyone who wants a fallback chain. n8n is a static graph, so true load-balancing would need an external counter; enabling the extra nodes gets fallback-on-failure without that infrastructure.

Per-sender memory

Conversation memory keys on the sender's address — context carries across a thread without one customer's history leaking into another's.

Attachments log independently

The main ops row comes from one agent call; attachments loop 0–N times. They get their own sheet tab correlated by messageId, so a slow or failed upload never blocks the row.

Zero-credential mock mode

Every credentialed node has a mock twin using canned data — the real routing logic runs unmodified, so the decision-making is demonstrable before any account exists.

Optional integrations, gated not required

Drive uploads and the LINE/Telegram/WhatsApp ops alerts each sit behind their own "Configured?" IF node keyed off an env var — leave it unset and that branch is simply never reached. Telegram/WhatsApp inbound triggers ship disabled for the same reason. A Gmail-only setup needs none of these accounts.

Tech stack

What's actually wired together.

Orchestrationn8n (cloud), @n8n/n8n-nodes-langchain
LLMOpenAI gpt-4o-mini (default) — Gemini 1.5-flash / Claude 3.5-sonnet wired as opt-in fallback, shipped disabled
MemoryLangChain buffer-window memory, keyed per sender
RetrievalQdrant vector store + OpenAI text-embedding-3-small
IntegrationsGmail, Telegram Bot API, WhatsApp Cloud API, Drive, Sheets, LINE, generic CRM REST
CRM backendCloudflare Worker + D1, auto-deployed via GitHub Actions
Run it yourself

No accounts required.

The mock workflow swaps every credentialed node for canned data — Gmail/Telegram/WhatsApp triggers, the AI Agent, Sheets, Drive, the CRM calls — but keeps the real routing switches untouched.

1 · Get n8n running

Free, local, no signup.

npx n8n

2 · Import the mock

Workflows → Import from File → mock/workflow.mock.json, then Execute Workflow.

3 · Inspect the CRM Worker

Read-only endpoint reference — serving real data from the deployed Worker + D1 database.

GET /v1/contacts/lookup?email=
Or skip the mock entirely

Deploy the real thing, one click at a time.

Your own n8n instance, your own CRM backend, your own OAuth credentials for Gmail/Drive/Sheets — no server to manage, no API keys to paste. SETUP.md walks through every step after you deploy.