Problem
The client ran on spreadsheets, scattered SaaS tools, and one over-worked operations team. Same data lived in three places. Invoicing waited on whoever opened the right email first. Reports were copy-pasted into PowerPoint on Sunday nights.
They had also tried Odoo before — it was installed, but barely used. Modules were either misconfigured for their workflow or so heavy with custom fields that nobody trusted the data.
Constraints
- Production ERP for a real business (not a sandbox)
- PostgreSQL with millions of rows already in legacy tables
- Strict cutover — finance had to keep closing the books every month
- Budget for one engineer leading, plus a junior pairing for knowledge transfer
My role
Senior Odoo Developer leading the work at ETTA Solutions (Jan 2024 – Jul 2024). I owned module architecture, database performance, AWS deployment, and the automation layer end to end. I also ran knowledge-transfer sessions for the internal team.
Architecture
Three layers, deliberately decoupled so each could be replaced without touching the others.
┌───────────────────────────────────────────────┐
│ Odoo (Python) │
│ Accounting · Inventory · HR · CRM modules │
└──────────────┬────────────────────────────────┘
│ REST + XML-RPC
┌──────────────▼────────────────────────────────┐
│ n8n workflows │
│ CRM ↔ marketing · ERP ↔ comms · ETL jobs │
└──────────────┬────────────────────────────────┘
│
┌──────────────▼─────────────┐ ┌─────────────┐
│ Twilio (SMS / WA / Voice)│ │ AWS S3 │
└────────────────────────────┘ └─────────────┘Infra was deployed on AWS via Terraform, behind an Nginx load balancer with autoscaling Odoo workers and a managed PostgreSQL instance.
Why n8n and not a SaaS automation tool
n8n self-hosts, has first-class HTTP + custom JS nodes, and lets us run private webhooks inside the same VPC as Odoo. For ERP data, "don't send our pricing to a third-party server" is a hard requirement.
Key decisions
1. Custom modules, but as thin as possible
Earlier attempts had over-customized core models. I kept each module focused:
acc_extensions— only the fields finance actually closes books withinv_warehouses_local— regional warehouse rules and unit conversionshr_local— payroll + leave with region-specific rulescrm_automation_bridge— the contract surface for n8n
The bridge module is the only place Odoo talks to the automation layer. That meant changing automation logic never required redeploying Odoo.
2. Postgres tuning before scaling out
Before adding workers, I profiled the slow queries. Most of the pain came from a handful of unindexed foreign keys, plus a couple of reports doing N+1 joins through ir.translation.
-- Example: invoice listing was scanning 2.1M rows
CREATE INDEX CONCURRENTLY idx_account_move_partner_state
ON account_move (partner_id, state)
WHERE state IN ('draft', 'posted');That single index, plus three more like it, cut p95 invoice list latency from ~1.4s to ~280ms. Only after that did we scale workers horizontally.
3. Terraform for everything, no console clicks
module "odoo_app" {
source = "./modules/ecs-service"
name = "odoo-prod"
desired_count = 3
min_capacity = 2
max_capacity = 8
cpu_target = 60
task_definition = data.template_file.odoo_task.rendered
subnets = module.vpc.private_subnets
}Disaster recovery went from "we hope" to "we restore in 22 minutes" because the entire environment is in code.
4. n8n + Twilio as the customer-facing layer
Every customer-facing event — order confirmed, invoice due, delivery dispatched — flowed through one n8n workflow set. Twilio handled the multi-channel delivery (SMS for cheap, WhatsApp for rich content, voice for high-value reminders).
The team no longer copy-pasted phone numbers into anything.
Results
- −40% time spent on manual processes (measured against the team's own weekly time logs)
- +25% performance gain across Odoo workloads after the Postgres and Terraform/AWS work
- 4 module domains stabilized: Accounting, Inventory, HR, CRM
- Multi-channel comms (SMS, voice, WhatsApp) running inside ERP workflows, not bolted on
- Two junior engineers fully ramped through paired implementation and weekly sessions
What I would do differently
- Set up structured logging (OpenSearch) on day one — debugging multi-system flows without it was painful for the first month
- Introduce a feature-flag layer for Odoo modules earlier — staging vs production drift was the most expensive bug class
- Treat n8n workflows as code from the start — versioned exports + a CI check, not the in-app editor as source of truth
Stack at a glance
- Backend / ERP: Odoo (Python), PostgreSQL
- Automation: n8n (self-hosted), Twilio
- Infra: AWS (ECS, RDS, S3), Nginx, Terraform, GitHub Actions
- Storage: S3 for documents, RDS Postgres for transactional data
Want a similar setup?
This is the engagement I take on most often as a freelancer — Odoo + automation + sane infra. See services or get in touch.