Telegram Service Handlers
1. What? — Definition and context
Section titled “1. What? — Definition and context”Service Handlers are a family of 4 N8N sub-workflows invoked by the Telegram Orchestrator once a user intent has been detected. Each handler encapsulates a functional domain (general chat, Odoo, Docker, N8N admin) and exposes a stable interface to the Orchestrator: it receives {service, action, params, chatId, userId, isAdmin} and returns {chatId, text, parseMode, keyboard} ready to send back to Telegram.
Components
Section titled “Components”| Workflow | ID | Nodes | Role |
|---|---|---|---|
| Service Handler - General | eCayuEQg4e8OhW6z | 10 | Help (help) + free chat with Claude (chat) |
| Service Handler - Odoo | QFvNDnHjV6MIBB7n | 33 | Contacts, invoices, quotes, opportunities, projects, tasks, dashboard |
| Service Handler - Docker | lBrSuWWM2WpN1v9I | 5 | Dispatcher to Docker Actions + Telegram formatting |
| Service Handler - N8N (parent) | lwY2qomOWjrx0EP8 | 6 | Admin gate + delegation to sub-workflow |
| SH N8N Action Executor (sub) | HpILpcfY8Z6OJq9b | 37 | Workflows, executions, datatables, triggers (extracted in #279) |
Position in the Telegram pipeline
Section titled “Position in the Telegram pipeline”2. Why? — Stakes and motivations
Section titled “2. Why? — Stakes and motivations”Problems solved
Section titled “Problems solved”| Problem | Without dedicated handlers | With Service Handlers |
|---|---|---|
| Orchestrator/business coupling | Orchestrator grows on every new action | Routing stays ~5 outputs; business lives elsewhere |
| Scattered permissions | isAdmin check repeated everywhere | Centralized gate in each sensitive handler |
| Fragile testing | Testing Odoo requires simulating the whole Telegram pipeline | Sub-workflow callable in isolation with a payload |
| Scalability | Adding /projet or N8N actions bloats an already critical workflow | The relevant handler absorbs growth |
Why extract SH N8N Action Executor (#279)
Section titled “Why extract SH N8N Action Executor (#279)”The Service Handler - N8N initially ran at 40 nodes: admin gate + 15 actions (list_workflows, toggle, executions, datatables, triggers…). Three symptoms triggered the refactoring into a thin parent + heavy sub-workflow:
| Symptom | Cause |
|---|---|
| Full re-test on each change | Modifying an action forced re-testing the admin gate |
| Pagination + callback everywhere | Pagination logic was duplicated across list flows |
| Hard to read | 40 nodes in one canvas hid the action Switch |
The split produces 6 parent nodes (Trigger → Check Admin → Is Admin? → Restore Input → Execute Sub → Return) and 37 nodes inside SH N8N Action Executor orchestrating the 15 N8N actions.
Why SH Odoo grew to 33 nodes (#187)
Section titled “Why SH Odoo grew to 33 nodes (#187)”Adding /projet commands pushed SH Odoo from 27 to 33 nodes: 3 new Switch outputs (list_projects, create_project, project_status), 5 additional native Odoo nodes, and a Code aggregator for the project dashboard. This is the upper bound before a split similar to #279 becomes necessary; see “What if?”.
Shared conventions
Section titled “Shared conventions”All handlers honor the same invariants:
| Invariant | Implementation |
|---|---|
| Single trigger | Execute Workflow Trigger with inputSource: passthrough |
| Send Typing | Telegram typing indicator while waiting |
| Format Response | Standard output {chatId, text, parseMode, keyboard.pattern} |
| Long messages | Split / paginate if > 4096 chars |
onError: continueRegularOutput | On external nodes (Odoo, HTTP) to format errors cleanly |
3. How? — Technical implementation
Section titled “3. How? — Technical implementation”Summary table
Section titled “Summary table”| Workflow | ID | Nodes | Role |
|---|---|---|---|
| Service Handler - General | eCayuEQg4e8OhW6z | 10 | Static help + Claude chat via CLI Ollama |
| Service Handler - Odoo | QFvNDnHjV6MIBB7n | 33 | 13 Odoo actions (contacts, invoices, projects, dashboard) |
| Service Handler - Docker | lBrSuWWM2WpN1v9I | 5 | Dispatch to Docker Actions + contextual keyboards |
| Service Handler - N8N | lwY2qomOWjrx0EP8 | 6 | Admin gate + sub-workflow call |
| SH N8N Action Executor | HpILpcfY8Z6OJq9b | 37 | Actual execution of N8N actions (extracted #279) |
Internal flow per handler
Section titled “Internal flow per handler”Admin gate (Odoo & N8N)
Section titled “Admin gate (Odoo & N8N)”Odoo and N8N actions are sensitive (data mutations, workflow execution). Both handlers query the same Telegram Authorized users Data Table (invf2IKyVDyzoBZr) and short-circuit when the flag is missing.
| Step | Behavior |
|---|---|
Lookup Telegram Authorized users by telegram_user_id | Retrieves user row |
is_admin missing or false | Immediate exit with ⛔ Admin-only action. |
is_admin === true | Continue business flow |
| Non-admin response | parseMode: HTML, keyboard.pattern: no_keyboard |
Service Handler - General · 10 nodes
Section titled “Service Handler - General · 10 nodes”Two actions: help (static HTML response), and chat (fallback, calls CLI Ollama /api/generate with model: codex-yolo, generated session_id). The request body is serialized via JSON.stringify to escape quotes and newlines — without this, text coming from Gemini Vision OCR with quotations broke the payload (fix d80e5ce).
Service Handler - Odoo · 33 nodes
Section titled “Service Handler - Odoo · 33 nodes”The handler implements 13 actions via a Switch with 13 outputs + fallback:
| Action | Odoo model | Specifics |
|---|---|---|
search_contact | res.partner | 3 parallel searches (name / email / phone) + Merge + Dedup |
create_contact / update_contact | res.partner | Update via Custom Resource with dynamic field |
search_invoice | account.move | Conditional IF on payment_state |
search_quote / search_opportunity | sale.order / crm.lead | Plain filters |
create_opportunity | crm.lead | Fields: name, email, phone, revenue, probability |
project_hours | account.analytic.line | Switch Period (week / month / all) |
search_project / search_task | project.project / project.task | Custom Resource + name like filter |
list_projects | project.project | All active projects (limit 15) |
create_project | project.project | Via /projet create <name> |
project_status | 3 models | Chain: Get Project → Tasks → Timesheets → Aggregate Code |
All Odoo nodes share the Odoo tool admin credential (xQmHuksl8E7nuwCV), onError: continueRegularOutput, and alwaysOutputData: true (top-level) so Format Response runs even on empty results.
Service Handler - Docker · 5 nodes
Section titled “Service Handler - Docker · 5 nodes”The handler stays thin because the actual work is delegated to Docker Actions (SSH into the VPS, run docker compose). The Parse Docker Params node translates the IA Router intent into a (dockerAction, dockerStack) pair:
- Passthrough if the action is already valid (
status,restart,logs,update,start,stop,list-all). - Otherwise mapping through
ACTION_MAP(container_status→status,restart_container→restart…). - Container → stack mapping via
CONTAINER_TO_STACK(n8n→n8n-stack,caddy→security-stack…). - Explicit error on unknown action (instead of a silent fallback on
status).
Format Response returns keyboard.pattern among standard / critical / list-all / after-action. The Orchestrator then applies the right Switch on the sending side (per the “Reply Markup is not expressionable” rule).
| Pattern | Use case | Buttons |
|---|---|---|
standard | Non-critical stack after status/logs | Status · Restart · Logs · Update · Back |
critical | security-stack (restart/stop/start/update blocked) | Status · Logs · Back |
list-all | All containers view | Status… · Restart… · Logs… · Back |
after-action | After restart/update | Status · Logs · Restart · Back |
Service Handler - N8N · 6 nodes + sub-workflow 37 nodes
Section titled “Service Handler - N8N · 6 nodes + sub-workflow 37 nodes”The parent only (1) gates admin via Data Table, (2) restores input, (3) delegates to SH N8N Action Executor via Execute Workflow. The sub-workflow exposes 15 actions:
| Family | Actions |
|---|---|
| Workflows | list_workflows (paginated), toggle_workflow |
| Executions | list_running, list_executions (status filters), view_execution, retry_execution |
| Data Tables | list_datatables, view_datatable (paginated), view_row, edit_row, delete_row |
| Manual triggers | list_triggers, list_trigger_cat, confirm_trigger, execute_trigger |
Long IDs (>64 bytes for Telegram callbacks) are stored in the n8n_pending_actions Data Table with an 8-char short ID and a 5-minute TTL. Manually triggerable workflows are declared in n8n_trigger_whitelist (categories backup / sync / cleanup / maintenance).
Long response handling
Section titled “Long response handling”Telegram caps messages at 4096 characters. Three patterns coexist:
| Pattern | When to use |
|---|---|
| Truncate | Simple lists, last message ends with … N results |
| Paginate | Long lists such as executions or data tables — buttons ◀️ N/M ▶️ |
| Multi-message | Execution details with stack trace — multiMessage: true, keyboard only on the last |
Referenced Data Tables
Section titled “Referenced Data Tables”| Data Table | ID | Used by |
|---|---|---|
Telegram Authorized users | invf2IKyVDyzoBZr | SH Odoo, SH N8N (admin gate) |
n8n_trigger_whitelist | — | SH N8N Action Executor (manual triggers) |
n8n_pending_actions | — | SH N8N Action Executor (short ID ↔ payload) |
n8n_datatable_metadata | — | SH N8N Action Executor (exposed tables) |
Standard input / output interface
Section titled “Standard input / output interface”Input (from Orchestrator):
{ "service": "odoo", "action": "search_contact", "params": { "query": "Dupont" }, "chatId": 123456789, "userId": 123456789, "isAdmin": true, "originalText": "Find contact Dupont"}Output (back to Orchestrator):
{ "chatId": 123456789, "text": "📇 <b>Contacts</b> (2 results)...", "parseMode": "HTML", "keyboard": { "pattern": "odoo_contacts" }}The Orchestrator reads only text, parseMode, and keyboard.pattern to route to the right Telegram node (Reply Markup structure is hardcoded per pattern in the UI — N8N does not support expressions on keyboard structure, only on button values).
4. What if? — Outlook and limits
Section titled “4. What if? — Outlook and limits”Current limits
Section titled “Current limits”| Limit | Impact | Mitigation |
|---|---|---|
| SH Odoo at 33 nodes | Canvas readability degrades | Split planned (by action family) following the #279 model |
| SH General is stateless | Free chat has no multi-turn history | Dedicated Conversation Agent handles multi-turn chats |
| SH Docker depends on SSH | Any SSH outage blocks all Docker actions | Docker Actions returns success/error, handler formats the error |
| Admin gate = column lookup | No fine-grained RBAC (just admin / non-admin) | Sufficient for solo use, to extend if multi-user |
Reply Markup not expressionable | Each pattern needs a dedicated Telegram node | Switch + hardcoded keyboards pattern (see telegram orchestrator doc) |
Evolution scenarios
Section titled “Evolution scenarios”If SH Odoo passes 40 nodes:
- Split by family (
SH Odoo Contacts,SH Odoo Projects,SH Odoo Sales) on the #279 model - Keep parent as admin gate + initial Switch dispatcher
- Benefit: isolated testing, adding actions no longer touches the rest
If finer-grained permissions are needed:
- Add columns to
Telegram Authorized users(can_docker,can_odoo,can_n8n) - Replace binary
is_admincheck by a capability-based check in each handler - Keep backward compatibility:
is_admin = trueimplies all capabilities
If Claude chat volume grows:
- SH General remains single-turn (limited usefulness for ongoing conversation)
- Route proactively to Conversation Agent beyond N messages in the same window
- Session caching on CLI Ollama via Redis (already in place)
If a new domain (e.g. direct monitoring):
- Create
Service Handler - Monitoring(Prometheus queries, Grafana dashboards) - Add a
monitoringoutput to the Orchestrator’s Route by Service - IA Router learns the new category through examples in its prompt
Related pages
Section titled “Related pages”Workflows
Section titled “Workflows”- Telegram Orchestrator — Caller of Service Handlers
- IA Router & MCP — Upstream intent detection
- Notification Hub — Similar routing pattern on the outbound side
- Docker Updates — Consumer of the Docker flow
- GitHub-Odoo Sync — Other family of extracted sub-workflows
Infrastructure
Section titled “Infrastructure”- Database Backup — Backups exposable via SH N8N triggers