← Back to Blog
Automation by

From 47 Emails a Day to One Dashboard: Automating Purchase Order Approvals in Odoo

From 47 Emails a Day to One Dashboard: Automating Purchase Order Approvals in Odoo

A procurement team was drowning in email-based PO approvals — 47 a day, scattered across inboxes, with no audit trail. Here's how n8n and Odoo fixed it.

Key Takeaways: Email-based approval chains for purchase orders are slow, invisible, and surprisingly easy to automate. An n8n workflow connected to Odoo’s JSON-RPC API can route POs by amount threshold, notify the right manager, and escalate if there’s no response — without any custom Odoo modules. The hard part isn’t the automation itself; it’s getting the threshold logic and escalation rules agreed on before you build anything.

The procurement manager opened her laptop Monday morning to 47 unread approval requests. Each one was a reply-all chain. Several were duplicates — someone had sent a follow-up because the first message got buried. Three were urgent because the supplier was holding delivery pending payment authorization.

This is not a technology problem. It’s an organizational pattern that technology can fix, once you understand what’s actually broken.

What “approval by email” really means at scale

The immediate pain is volume. But the underlying problem is that email approval has no state. There’s no single place that shows which POs are pending, who hasn’t responded, and how long they’ve been waiting. When a manager is on leave, requests pile up with no automatic escalation. When a purchase gets approved, the confirmation might land in a spam folder.

Vietnamese companies often run multi-level approval hierarchies — a line manager approves first, a department head approves second, and anything above a certain amount needs a CFO sign-off. In email, that chain is invisible. In Odoo, purchase.order has a state field (draft, sent, purchase, done, cancel), but it doesn’t track who approved what or when. The chaser emails that accumulate in a team’s inbox are the symptom of state that lives nowhere.

The fix: make approval state explicit and route it automatically.

The Odoo side: what already exists, and what it doesn’t do

Odoo’s Purchase module has a native three-tier approval mechanism. You can configure it under Purchase > Configuration > Settings:

  • No validation required: any user with PO creation rights can confirm a PO directly
  • Approval for orders above a minimum amount: POs above a threshold require a manager to approve
  • Always require approval: every PO needs manager sign-off before it can move to purchase state

That built-in approval triggers the standard Odoo chatter notification — a mail.thread message logged on the purchase.order record, with a notification sent to the responsible user. It works. The problem is that “responsible user” is a single field (user_id on purchase.order), and the notification goes to that user’s Odoo inbox. If your team lives in email and doesn’t check Odoo daily, the notification disappears into noise.

What Odoo doesn’t provide out of the box:

  • Routing by amount to different approvers (CFO for anything above 50M VND, department head for anything below)
  • Escalation if the primary approver hasn’t responded in 24 hours
  • A consolidated daily digest instead of one notification per PO
  • Any of this flowing into Slack, Teams, or email in a controlled format

That’s where n8n comes in.

The n8n workflow: approval routing by amount threshold

The workflow runs every 15 minutes, polling Odoo for purchase orders in sent state (submitted for approval but not yet confirmed). It pulls the relevant fields via JSON-RPC, routes each PO to the right approver based on amount, and tracks whether a notification has already been sent.

Polling step — Odoo JSON-RPC call:

{
  "jsonrpc": "2.0",
  "method": "call",
  "params": {
    "service": "object",
    "method": "execute_kw",
    "args": [
      "your-db",
      uid,
      "your-api-key",
      "purchase.order",
      "search_read",
      [[["state", "=", "sent"]]],
      {
        "fields": [
          "id", "name", "partner_id", "amount_total",
          "user_id", "date_order", "order_line"
        ],
        "limit": 50
      }
    ]
  }
}

Amount-based routing logic (n8n Switch node):

amount_total >= 50,000,000 VND  → Route A: CFO approval
amount_total >= 10,000,000 VND  → Route B: Department head approval
amount_total < 10,000,000 VND   → Route C: Line manager approval

Each route sends a structured notification — not a raw “you have a PO waiting” message, but a summary that includes supplier name, order amount, line items count, and a direct link to the Odoo record. Approvers get a single message with enough context to approve or reject without opening Odoo.

The approval action itself still happens in Odoo. The workflow just makes sure the right person gets notified and reminded.

Escalation timer:

A separate n8n workflow runs daily and checks the same sent state POs, comparing date_order against today. Any PO that has been waiting more than 24 hours without state change triggers an escalation notification to the approver’s manager. After 48 hours, it adds a daily summary email to the CFO inbox.

This sounds aggressive. In practice, it’s the first time most approval chains have a deadline attached to them.

State tracking: avoiding duplicate notifications

The trickiest part of this setup is making sure n8n doesn’t notify the same approver 20 times. The workflow uses a simple n8n static data store (or a lightweight Postgres table if you want persistence across restarts) to record which PO IDs have already received notifications and at which escalation level.

On each poll:

  1. Fetch all POs in sent state
  2. Filter out IDs already in the notification log
  3. Send notifications for new ones
  4. Log new IDs with timestamp and level

When an approver acts — confirming or rejecting the PO — Odoo changes the state field. On the next poll, the PO no longer appears in the sent state query, and the workflow stops tracking it.

This is much simpler than building a full audit table. The trade-off: if your n8n instance restarts and you lose the static store, there’s a single round of duplicate notifications. That’s acceptable for most teams. If it isn’t, use a Postgres table.

The results after 60 days

The team went from 47 approval emails per day to zero. That sounds like marketing copy; it isn’t. The email volume dropped because approval requests now go through n8n directly to the approver’s preferred channel (Slack for the operations team, email for the CFO), and the chaser emails stopped because escalation is automatic.

Concrete numbers from the first two months:

  • Average approval cycle time: from 2.3 days to 6.4 hours
  • POs approved within 24 hours: from 41% to 89%
  • Approval-related emails per day: from 47 to 3 (the 3 are legitimate exceptions — POs needing clarification before approval)
  • Supplier complaints about delayed purchase confirmation: from a standing agenda item in weekly ops reviews to zero

The error rate metric is harder to quantify. The team previously had occasional POs confirmed twice (a human clicking “Confirm” in Odoo while an approval email chain was still in progress). That stopped, because the approval state is now visible in one place.

What this doesn’t fix

Honest accounting matters here. The workflow assumes that your purchase.order.line data is clean — that each PO has an accurate supplier, amount, and product reference before it enters the approval queue. If your procurement team is creating POs with placeholder amounts and correcting them later, the routing logic will route them wrong. The workflow is only as good as the data it reads.

It also doesn’t handle partial approvals. Some procurement processes allow a manager to approve some line items and reject others. Odoo’s native approval is all-or-nothing at the PO level, and this workflow mirrors that constraint. If you need line-item approval, that’s a custom module — not a workflow problem.

Finally, the n8n-to-approver notification channel needs to be one your approvers actually check. Rolling this out to a team that hasn’t enabled Slack notifications yet just moves the ignored message from email to Slack.

Transferable lessons

Three things apply to any approval automation in Odoo, regardless of the specific workflow:

  1. State is the core primitive. Everything in Odoo approval workflows depends on the state field. Before building anything, map out what state transitions correspond to what business events. In purchase.order, sent is “awaiting approval” and purchase is “approved.” Know these before you write a single n8n node.

  2. Agree on threshold logic before you build. The question “who approves POs above 10M VND?” sounds simple. In practice, it will surface three different answers from three different managers. Get that conversation done on a whiteboard before any automation is in place.

  3. Escalation must be automatic. Approval workflows that rely on humans to follow up on humans reliably revert to email within two weeks. The escalation timer is what makes the system self-maintaining.

This same pattern — poll Odoo state, route based on field values, notify via external channel, escalate on timeout — works for invoice approval, expense approval, and contract sign-off. We covered a similar approach for uninvoiced sales orders in How to Alert Uninvoiced Sales Orders in Odoo Using n8n. The mechanics are identical; only the modelA mathematical function trained on data that maps inputs to outputs. In ML, a model is the artifact produced after training — it encapsulates learned patterns and is used to make predictions or… and the fields change.


Key Takeaways

  • Odoo’s native PO approval covers the basics but notifies a single user with no escalation logic
  • n8n + JSON-RPC polling can route approvals by amount threshold and escalate on inactivity without any custom Odoo code
  • Track notification state explicitly to avoid duplicate messages — a simple ID log is enough for most teams
  • Approval cycle time and email volume are the metrics that move; both improved substantially within the first two months
  • The workflow is built on state = 'sent' polling — every other featureAn individual measurable property or characteristic of the data used as input to a model. Feature engineering — selecting, transforming, and creating features — is a critical step in the ML pipeline. follows from that single field

At Trobz, we build and maintain n8n-to-Odoo automation for procurement, finance, and operations teams — if you’re mapping your own approval workflow and want a second opinion on the routing logic, reach out.

Ready to put AI to work?

Let's explore how Trobz AI can automate your processes, enhance your ERP, and help your team make better decisions — faster.