Late Payments Are Not a Collections Problem—They're a Systems Problem
The average B2B invoice in 2026 is paid 15 days past its due date. For a mid-market company billing $2M per month, that's $1M in working capital permanently trapped in accounts receivable. Most finance teams respond by assigning a junior accountant to manually email overdue customers from a spreadsheet—a process that is inconsistent, unscalable, and almost always deprioritized when month-end close rolls around.
Manual follow-up fails because it relies on human memory and discretion. The friendly reminder at 7 days overdue doesn't go out because the accountant was reconciling bank statements. The firm notice at 30 days gets skipped because "that customer always pays eventually." The final warning at 60 days never escalates to legal because nobody wants to damage the relationship. Meanwhile, your DSO (Days Sales Outstanding) creeps up, your cash conversion cycle stretches, and your CFO starts asking why the credit line keeps growing.
Odoo 19's payment follow-up module solves this by turning your dunning process into a rules-based, automated workflow. You define the escalation levels, design the email templates, set the triggers, and let the system execute. Every overdue invoice gets the right message at the right time—no spreadsheets, no forgotten follow-ups, no awkward "I think we already emailed them" conversations. This guide walks you through the complete setup: configuring follow-up levels, designing dunning templates, automating actions, scheduling sends, and measuring the impact on your DSO.
Configuring Follow-Up Levels: From Friendly Reminder to Legal Escalation
Odoo's follow-up system is built around follow-up levels—a tiered escalation path that defines what happens at each stage of overdue payment. Each level specifies the number of days after the due date, the communication channel, and the action to take. The key is designing levels that balance firmness with customer relationship preservation.
Navigating to Follow-Up Configuration
In Odoo 19, follow-up levels are configured under Accounting → Configuration → Follow-Up Levels. If you don't see this menu, ensure the account_followup module is installed. On Odoo.sh and Odoo Online, it's bundled with the Accounting app. On-premise installations need to install it explicitly.
Recommended Four-Level Dunning Structure
| Level | Days Overdue | Tone | Channel | Action |
|---|---|---|---|---|
| Level 1: Friendly Reminder | 7 days | Polite, assumes oversight | Send reminder email with invoice PDF attached | |
| Level 2: Firm Notice | 21 days | Professional, references terms | Email + SMS | Send follow-up email, SMS notification, log activity on partner |
| Level 3: Final Warning | 45 days | Urgent, mentions consequences | Email + SMS | Send final notice, block new sales orders, notify sales rep |
| Level 4: Legal / Collections | 60 days | Formal, legal language | Email (certified) | Send legal notice, assign to collections team, block all transactions |
Creating Follow-Up Levels via the UI
# Navigate to: Accounting → Configuration → Follow-Up Levels
# Click "New" to create each level
# Level 1 settings:
Name: "Friendly Reminder"
Delay (days): 7
Description: "First polite reminder for overdue payment"
Send Email: ✓ (checked)
Send SMS: ✗ (unchecked)
Send Letter: ✗ (unchecked)
Manual Action: ✗ (unchecked)
Join Open Invoices: ✓ (checked)
# Level 2 settings:
Name: "Firm Notice"
Delay (days): 21
Send Email: ✓ (checked)
Send SMS: ✓ (checked)
Manual Action: "Log a note on the partner record"
Join Open Invoices: ✓ (checked)
# Level 3 settings:
Name: "Final Warning"
Delay (days): 45
Send Email: ✓ (checked)
Send SMS: ✓ (checked)
Manual Action: "Notify account manager via internal note"
Auto Execute: ✓ (checked)
Join Open Invoices: ✓ (checked)
# Level 4 settings:
Name: "Legal / Collections"
Delay (days): 60
Send Email: ✓ (checked)
Manual Action: "Assign to collections team"
Auto Execute: ✗ (requires manual confirmation)
Join Open Invoices: ✓ (checked)The delay in days is calculated from the invoice due date, not the invoice date. If your payment terms are "Net 30" and you set Level 1 at 7 days, the first reminder goes out 37 days after the invoice was issued (30-day term + 7-day delay). Many teams misconfigure this by thinking the delay starts from the invoice creation date. Double-check by reviewing the "Next Follow-Up Date" on a test partner.
Designing Email Templates for Each Dunning Level
The default follow-up email templates in Odoo are functional but generic. They read like system notifications—impersonal, blunt, and often missing the context a customer needs to action the payment. Effective dunning emails follow a specific escalation curve: warm at Level 1, informative at Level 2, firm at Level 3, and formal at Level 4.
Level 1: The Friendly Reminder Template
Subject: Friendly reminder – Invoice {{ object.ref or '' }} is past due
<p>Dear {{ object.commercial_partner_id.name }},</p>
<p>
We hope this message finds you well. Our records indicate
that the following invoice(s) are now past their due date:
</p>
{{ followup_table }}
<p>
If payment has already been sent, please disregard this
notice and accept our thanks. If not, we would appreciate
payment at your earliest convenience.
</p>
<p>
For your reference, our bank details are included on the
attached invoice. If you have any questions about these
charges, please reply to this email and we'll respond
within one business day.
</p>
<p>Best regards,<br/>{{ object.company_id.name }} — Accounts Receivable</p>Level 2: The Firm Notice Template
Subject: Payment overdue – Action required for {{ object.ref or 'your account' }}
<p>Dear {{ object.commercial_partner_id.name }},</p>
<p>
Despite our previous reminder, the following invoice(s)
remain unpaid and are now significantly overdue:
</p>
{{ followup_table }}
<p>
<strong>Total overdue: {{ total_due }}</strong>
</p>
<p>
Per our agreed payment terms, payment was due on the dates
listed above. We kindly request that you arrange payment
within the next <strong>7 business days</strong>.
</p>
<p>
If there is a dispute or issue preventing payment, please
contact us immediately so we can resolve it together. We
value our business relationship and want to find a workable
solution.
</p>
<p>Regards,<br/>{{ object.company_id.name }} — Finance Team</p>Level 3: The Final Warning Template
Subject: FINAL NOTICE – Overdue account {{ object.ref or '' }} requires immediate payment
<p>Dear {{ object.commercial_partner_id.name }},</p>
<p>
This is our final notice regarding the following
outstanding balance on your account:
</p>
{{ followup_table }}
<p>
<strong>Total overdue: {{ total_due }}</strong>
</p>
<p>
We have attempted to reach you on multiple occasions
without resolution. Please be advised that if full payment
is not received within <strong>7 calendar days</strong>
from the date of this notice:
</p>
<ul>
<li>Your account will be placed on credit hold</li>
<li>New orders will not be processed</li>
<li>The matter may be referred to our collections department</li>
</ul>
<p>
To avoid these actions, please remit payment immediately or
contact our finance team to arrange a payment plan.
</p>
<p>Sincerely,<br/>{{ object.company_id.name }} — Credit Management</p> The {{ followup_table }} placeholder is a special variable provided by Odoo's follow-up engine. It renders an HTML table listing all overdue invoices for that partner: invoice number, date, due date, amount due, and amount remaining. You don't need to build this table manually. If you want to customize its columns, you'll need to override the _get_followup_table method on the res.partner model.
Automating Follow-Up Actions: Email, SMS, Block Sales, and Collections Assignment
Sending emails is only half the dunning equation. Effective follow-up requires automated actions that escalate pressure without human intervention. Odoo 19 lets you chain multiple actions to each follow-up level: sending communications, blocking business operations, logging activities, and routing to specialized teams.
Blocking Sales Orders for Overdue Customers
One of the most powerful follow-up actions: automatically preventing new sales orders for customers who have passed a certain overdue threshold. This prevents the classic problem of shipping $50,000 in new product to a customer who hasn't paid last quarter's $50,000 invoice.
# In Odoo 19, the follow-up level can trigger a
# "Manual Action" or an automated server action.
# To block sales, use the built-in partner trust mechanism:
# Method 1: Use the built-in "Block" action on Level 3
# In Follow-Up Level configuration:
# → Action: "Block the partner"
# This sets partner.payment_next_action_date and triggers
# a warning on sales order creation.
# Method 2: Custom server action for granular control
# Navigate to: Settings → Technical → Server Actions
# Server Action configuration:
Action Name: "Block Sales for Overdue Partner"
Model: res.partner
Action Type: Execute Python Code
# Python code:
for partner in records:
# Set the partner's invoice status to "blocked"
partner.sudo().write({
'invoice_warn': 'block',
'invoice_warn_msg': (
'CREDIT HOLD: This customer has invoices '
'overdue by 45+ days. New sales orders '
'require approval from the Finance team. '
f'Total overdue: {partner.total_due} '
f'{partner.currency_id.symbol}'
),
})
# Log the action
partner.message_post(
body='Sales blocked due to overdue follow-up '
'reaching Level 3.',
message_type='notification',
subtype_xmlid='mail.mt_note',
)SMS Notifications for Urgent Follow-Ups
Email follow-ups have open rates around 20-30% for transactional messages. SMS open rates exceed 95%. For Level 2 and Level 3, adding an SMS notification dramatically increases the likelihood of response. In Odoo 19, SMS follow-up requires the sms module and an active IAP (In-App Purchase) credit balance for SMS sending.
# SMS Template (160 chars recommended):
{{ object.company_id.name }}: Your account has
{{ total_due }} overdue. Please arrange payment
to avoid service interruption. Questions? Call
{{ object.company_id.phone }}Assigning to Collections Team
At Level 4, the automated system hands off to humans. The key is making the handoff clean: the collections team needs context, not just a name and a number. Configure the manual action to create an activity assigned to the collections team with all relevant details:
# Server action: Assign to Collections Team
for partner in records:
# Create a scheduled activity for the collections team
partner.activity_schedule(
activity_type_xmlid='mail.mail_activity_data_todo',
summary=f'Collections: {partner.name} – '
f'{partner.total_due} '
f'{partner.currency_id.symbol} overdue',
note=(
f'<p><strong>Partner:</strong> '
f'{partner.name}</p>'
f'<p><strong>Total Overdue:</strong> '
f'{partner.total_due} '
f'{partner.currency_id.symbol}</p>'
f'<p><strong>Oldest Due Date:</strong> '
f'{partner.oldest_due_date}</p>'
f'<p>All automated follow-up levels have '
f'been exhausted. Manual collection action '
f'required.</p>'
),
# Assign to the collections team lead
user_id=env.ref(
'my_module.collections_team_lead'
).id,
date_deadline=fields.Date.today()
+ timedelta(days=3),
)Each follow-up level has an "Auto Execute" toggle. When enabled, the follow-up action fires automatically during the scheduled cron job. When disabled, the action appears in the follow-up report for manual review and confirmation. Best practice: auto-execute Levels 1 and 2 (low risk, high volume), require manual confirmation for Levels 3 and 4 (high impact, needs human judgment). A misfired Level 3 that blocks a key customer's orders can cause serious relationship damage.
Setting Up the Follow-Up Report and Scheduling Automatic Sends
With follow-up levels and templates in place, the final configuration step is setting up the execution engine: the follow-up report and the scheduled cron job that processes it. This is where the "automate" in automated dunning actually happens.
The Follow-Up Report
Navigate to Accounting → Customers → Follow-Up Reports. This screen shows every partner with overdue invoices, grouped by their current follow-up level. For each partner, you see:
- Partner name and contact details
- Current follow-up level (which level was last sent)
- Next action date (when the next level triggers)
- Total overdue amount across all unpaid invoices
- Oldest due date to gauge severity
- Responsible user (the salesperson or account manager)
From this report, you can process follow-ups in bulk (select all, click "Process") or review individual partners before sending. The report also supports filtering by follow-up level, salesperson, or amount threshold—useful for prioritizing high-value overdue accounts.
Configuring the Cron Job for Automatic Processing
# Navigate to: Settings → Technical → Scheduled Actions
# Find: "Follow-up email for overdue invoices"
# Recommended settings:
Action Name: Follow-up emails (auto)
Model: res.partner
Execute Every: 1 Day
Next Execution Date: [set to tomorrow 07:00 AM]
Number of Calls: -1 (unlimited / run forever)
Active: ✓
# The cron calls:
# self.env['res.partner']._cron_execute_followup_company()
#
# This method:
# 1. Queries all partners with overdue invoices
# 2. Checks each partner's current follow-up level
# 3. Determines if the next level is due (based on days)
# 4. For auto-execute levels: sends the communication
# 5. For manual levels: marks them for review in the report
# 6. Updates the partner's follow-up status
# IMPORTANT: The cron processes ONE company at a time.
# Multi-company setups need the cron enabled per company.
# Check Settings → Technical → Scheduled Actions and
# ensure the cron exists for each company.Timing Your Follow-Up Sends
When you schedule the cron matters more than you'd think. Follow-up emails sent at Tuesday 9:00 AM in the recipient's timezone get the highest response rates for B2B collections. Avoid Monday mornings (inbox overload), Friday afternoons (checked out), and end-of-month (AP teams are busy closing). If your customers span multiple timezones, schedule the cron for early morning UTC so it lands in business hours across North America and Europe.
Before enabling the cron in production, test the entire flow with a single partner. Create a test partner, post a backdated invoice (due date 60+ days ago), and manually trigger the follow-up from the report. Verify that all four levels fire correctly, that the email content renders properly, that the SMS sends, and that the sales block activates. One misconfigured template sending garbled emails to your entire customer base is a relationship disaster that takes months to repair.
Tracking Follow-Up Effectiveness with Aging Reports and DSO Metrics
Setting up automated dunning is useless if you can't measure its impact. The two metrics that matter most: the accounts receivable aging report (how much money is overdue and for how long) and DSO (how many days it takes, on average, to collect payment after invoicing). Odoo 19 provides both natively—but extracting actionable insights requires knowing where to look.
The Aged Receivable Report
Navigate to Accounting → Reporting → Aged Receivable. This report buckets your outstanding receivables into aging periods: Current, 1-30 days, 31-60 days, 61-90 days, and 90+ days. The default periods align with standard financial reporting, but you can customize them under the report's options menu.
| Metric | Where to Find It | What It Tells You | Target Benchmark |
|---|---|---|---|
| DSO | Custom dashboard or spreadsheet export | Average days from invoice to payment | < 40 days for B2B |
| Aging Concentration | Aged Receivable report | % of AR in each bucket (current vs. 90+) | > 80% in Current + 1-30 |
| Follow-Up Response Rate | Follow-Up Report + payment reconciliation | % of partners who pay after each level | > 50% at Level 1, > 30% at Level 2 |
| Collections Effectiveness Index (CEI) | Custom calculation | (Beginning AR + Credit Sales - Ending AR) / (Beginning AR + Credit Sales) × 100 | > 80% |
Calculating DSO in Odoo
# DSO = (Accounts Receivable / Net Credit Sales) × Days
# Odoo doesn't surface DSO as a built-in KPI, but you can
# compute it with a scheduled action or a custom dashboard.
from datetime import date, timedelta
from odoo import api, fields, models
class ResCompany(models.Model):
_inherit = 'res.company'
x_dso = fields.Float(
string='Days Sales Outstanding',
compute='_compute_dso',
digits=(6, 1),
)
@api.depends_context('force_dso_recompute')
def _compute_dso(self):
today = fields.Date.today()
period_start = today - timedelta(days=90)
for company in self:
# Total outstanding receivables (unpaid invoices)
receivable = sum(
self.env['account.move'].search([
('company_id', '=', company.id),
('move_type', 'in',
['out_invoice', 'out_refund']),
('payment_state', '!=', 'paid'),
('state', '=', 'posted'),
]).mapped('amount_residual')
)
# Net credit sales in the period
credit_sales = sum(
self.env['account.move'].search([
('company_id', '=', company.id),
('move_type', '=', 'out_invoice'),
('state', '=', 'posted'),
('invoice_date', '>=', period_start),
('invoice_date', '<=', today),
]).mapped('amount_total')
)
days_in_period = 90
if credit_sales:
company.x_dso = (
receivable / credit_sales
) * days_in_period
else:
company.x_dso = 0.0Building a Follow-Up Effectiveness Dashboard
The most actionable view combines aging data with follow-up activity. Track these metrics weekly:
- Level 1 conversion rate: What percentage of partners pay within 7 days of the friendly reminder? If it's below 40%, your Level 1 template may be too soft or arriving too late.
- Escalation rate: What percentage of partners reach Level 3 or Level 4? If more than 15% of your dunning cases escalate to final warning, you may have a customer onboarding problem (bad payment terms) rather than a collections problem.
- DSO trend line: Plot your monthly DSO over time. After enabling automated follow-ups, you should see a measurable decline within 60-90 days. If DSO doesn't move, check whether the cron is actually running and whether your templates are rendering correctly.
- Top delinquent accounts: The Pareto principle applies to AR. Typically 20% of your customers represent 80% of your overdue balance. These accounts need personalized attention beyond automated dunning.
3 Follow-Up Configuration Mistakes That Sabotage Your Dunning Process
Confusing Follow-Up Levels with Payment Terms
This is the most common misconfiguration we see in Odoo implementations. A finance manager sets payment terms to "Net 30" and then creates a follow-up Level 1 at "30 days"—thinking the reminder will go out when the invoice is 30 days old. It won't. Follow-up delays are calculated from the due date, not the invoice date. With Net 30 terms and a 30-day Level 1 delay, the first reminder goes out 60 days after the invoice was created. By that time, the customer has forgotten the invoice exists.
Map your follow-up levels to days past the due date, not days past the invoice date. For Net 30 terms, Level 1 at 7 days means the first reminder arrives on day 37 from invoicing. Document this mapping in a table for your finance team: "Invoice Date + Payment Terms + Follow-Up Delay = Actual Send Date." Then validate it by creating test invoices with known dates and verifying the follow-up report shows the correct "Next Action Date."
Multi-Company Follow-Up Sending from the Wrong Entity
In a multi-company Odoo setup, follow-up emails are sent based on the company of the overdue invoice. But the sender email address is determined by the mail.catchall.domain and mail.default.from system parameters—which are global, not per-company. Result: your subsidiary "Brand B" sends dunning emails from accounting@brand-a.com. Customers are confused, some mark it as phishing, and your carefully designed dunning sequence loses all credibility.
Configure company-specific email aliases under each company's settings (Settings → General Settings → Discuss). Set the "Email Alias Domain" per company. Then verify the follow-up email template uses {{ object.company_id.email }} as the reply-to address, not a hardcoded email. Test by triggering a follow-up for each company and checking the actual "From" header in the received email. In Odoo 19, the mail.mail record shows the exact headers that were sent.
Dunning Disputed Invoices (and Destroying Customer Relationships)
A customer disputes an invoice because the delivered quantity didn't match the PO. Your support team is investigating. Meanwhile, the automated dunning cron fires and sends a "Final Warning" email threatening to block their account and involve legal—for an invoice your own team acknowledges is under dispute. The customer is furious, the account manager is blindsided, and the relationship takes months to repair.
Use the "Exclude from Follow-Up" flag on the partner record or individual invoices. In Odoo 19, navigate to the partner's Accounting tab and check "Exclude from Follow-Up" to pause all dunning for that partner. For invoice-level control, add a custom boolean field x_followup_excluded on account.move and extend the follow-up query to filter it out. Critical process: create a workflow where any invoice dispute automatically triggers the exclusion flag. When the dispute is resolved, the flag is cleared and the dunning sequence resumes from the appropriate level.
The Financial Impact of Automated Dunning in Odoo
Automated payment follow-up isn't a "nice to have" for finance teams. It's a working capital multiplier:
Companies that implement tiered automated dunning typically see DSO drop from 50+ days to 35 days within two quarters. For a $5M annual revenue business, that's $411,000 in freed working capital.
Manual follow-up consumes 3-4 hours per week for a mid-sized AR team. Automated dunning reduces this to exception handling only—reviewing Level 3/4 escalations and dispute management.
Consistent, timely follow-up catches delinquent accounts before they become uncollectible. The biggest predictor of write-off risk is how long an invoice goes without any follow-up contact.
The math is straightforward. If your average monthly revenue is $2M and your current DSO is 52 days, reducing it to 36 days (a 30% improvement) frees up $1.07M in working capital. That's money you can deploy for growth, debt reduction, or simply reducing your reliance on credit facilities. The entire Odoo follow-up configuration takes 2-3 days to implement and test. The ROI is measured in weeks, not years.
Optimization Metadata
Complete guide to automating payment follow-up in Odoo 19. Configure dunning levels, design email templates, schedule automatic sends, and reduce DSO by 30%.
1. "Configuring Follow-Up Levels: From Friendly Reminder to Legal Escalation"
2. "Designing Email Templates for Each Dunning Level"
3. "Automating Follow-Up Actions: Email, SMS, Block Sales, and Collections Assignment"
4. "Tracking Follow-Up Effectiveness with Aging Reports and DSO Metrics"
5. "3 Follow-Up Configuration Mistakes That Sabotage Your Dunning Process"