Skip to content

Email Routing in Odoo

How Odoo routes incoming emails to the correct records (Purchase Orders, Sales Orders, Helpdesk Tickets, etc.).

Overview

Odoo uses a sophisticated email routing system that automatically attaches incoming emails to the correct records. When you send an email from a Purchase Order (PO0001) and the vendor replies, that reply is automatically attached to PO0001's chatter.

How It Works

Data Flow

┌──────────────────────────────────────────────────────────────────┐
│                     EMAIL ROUTING FLOW                            │
└──────────────────────────────────────────────────────────────────┘

   YOU (Odoo)                                         VENDOR
       │                                                 │
       │  Send RFQ Email from PO0001                    │
       │  Message-ID: <purchase.order-123-xxx@domain>   │
       │────────────────────────────────────────────────►│
       │                                                 │
       │                                                 │
       │◄────────────────────────────────────────────────│
       │  Vendor Reply                                   │
       │  In-Reply-To: <purchase.order-123-xxx@domain>  │
       │                                                 │
┌──────────────────┐
│  Mail Server     │
│  (Gmail/Office)  │
└────────┬─────────┘
┌──────────────────┐     ┌─────────────────────────────────┐
│  Fetchmail Cron  │────►│  message_route()                │
│  (every 5 min)   │     │                                 │
└──────────────────┘     │  1. Parse In-Reply-To header    │
                         │  2. Find: purchase.order, id=123│
                         │  3. Attach to PO0001 chatter    │
                         └─────────────────────────────────┘

Step 1: Sending Email from Odoo

When you send an email from a Purchase Order, Odoo automatically adds special headers:

Message-ID: <purchase.order-123-1702500000@yourdomain.com>
References: <purchase.order-123-1702500000@yourdomain.com>
Reply-To: "YourCompany" <catchall@yourdomain.com>

The Message-ID format encodes the record information:

<model_name-record_id-timestamp@domain>

For example: - purchase.order-123 → Purchase Order with ID 123 - sale.order-456 → Sales Order with ID 456 - helpdesk.ticket-789 → Helpdesk Ticket with ID 789

Step 2: Vendor/Customer Replies

When someone replies to the email, their email client automatically includes:

In-Reply-To: <purchase.order-123-1702500000@yourdomain.com>
References: <purchase.order-123-1702500000@yourdomain.com>

These headers reference the original Message-ID.

Step 3: Fetchmail Cron Job

The fetchmail module runs a scheduled action that:

  1. Connects to your incoming mail server (IMAP/POP3)
  2. Downloads new emails
  3. Passes each email to mail.thread.message_process()
Scheduled Action: "Mail: Fetchmail Service"
Model: fetchmail.server
Method: _fetch_mails()
Default Interval: 5 minutes

Step 4: Email Routing Logic

The core routing happens in mail.thread.message_route():

def message_route(self, message, message_dict, model=None, thread_id=None):
    """
    Route incoming email to the correct record.

    Routing priority:
    1. Check 'In-Reply-To' header → find original message → get record
    2. Check 'References' header → find original message → get record
    3. Check email 'To' address → match mail.alias → create/find record
    4. Fallback to catchall alias
    """

Routing Priority:

Priority Method Description
1 In-Reply-To Parse header, find original message, get record
2 References Same as above, fallback if In-Reply-To fails
3 Mail Alias Match To address to mail.alias records
4 Catchall Route to default model or reject

Configuration

1. Incoming Mail Server (Fetchmail)

Navigate to: Settings → Technical → Email → Incoming Mail Servers

Field Description
Name Descriptive name
Server Type IMAP (recommended) or POP
Server Name mail.yourdomain.com
Port 993 (IMAP SSL) or 995 (POP SSL)
SSL/TLS Should be enabled
Username Email address or username
Password Email password or app password
Active Must be checked

Test Connection

Always click "Test Connection" after configuring to verify settings.

2. Fetchmail Scheduled Action

Navigate to: Settings → Technical → Scheduled Actions

Search for: "Mail: Fetchmail Service"

Field Recommended Value
Active Checked
Interval Number 5
Interval Unit Minutes
Number of Calls -1 (unlimited)

3. Alias Domain

Navigate to: Settings → General Settings → Discuss

Set Alias Domain to your email domain (e.g., yourdomain.com)

This is used for generating reply-to addresses like purchase+123@yourdomain.com.

4. Outgoing Mail Server

Navigate to: Settings → Technical → Email → Outgoing Mail Servers

Field Description
SMTP Server smtp.yourdomain.com
SMTP Port 587 (TLS) or 465 (SSL)
Connection Security TLS or SSL
Username Your email address
Password Your password

Database Tables

Table Purpose
fetchmail_server Incoming mail server configuration
mail_alias Email aliases for routing
mail_message All messages with Message-ID
mail_mail Outgoing email queue
ir_cron Scheduled actions including fetchmail

Check Settings via SQL

-- Check if fetchmail cron is active
SELECT name, active, interval_number, interval_type, nextcall
FROM ir_cron
WHERE name ILIKE '%fetchmail%';

-- Check incoming mail servers
SELECT name, server, state, is_ssl, active
FROM fetchmail_server;

-- Check mail alias domain
SELECT key, value
FROM ir_config_parameter
WHERE key ILIKE '%alias%';

-- Check recent incoming messages
SELECT id, message_id, model, res_id, date, subject
FROM mail_message
WHERE message_type = 'email'
ORDER BY date DESC
LIMIT 20;

Troubleshooting

Email Not Routing to Record

Symptoms: Vendor replies don't appear in PO chatter

Check:

  1. Fetchmail cron is active:

    Settings → Technical → Scheduled Actions → "Mail: Fetchmail Service"
    

  2. Incoming mail server is connected:

    Settings → Technical → Email → Incoming Mail Servers
    → Test Connection
    

  3. Check Odoo logs:

    sudo grep -i "fetchmail\|mail\|routing" /var/log/odoo/odoo.log | tail -50
    

  4. Run fetchmail manually:

  5. Go to Scheduled Actions
  6. Find "Mail: Fetchmail Service"
  7. Click "Run Manually"

Common Issues

Issue Cause Solution
Cron not running Disabled after update Re-enable in Scheduled Actions
Connection failed Password changed Update incoming mail server password
SSL error Certificate issue Check SSL/TLS settings
Emails not found Wrong server/folder Verify IMAP folder settings
Routing to wrong record Header mismatch Check original Message-ID

Check Logs for Errors

# Check fetchmail activity
sudo grep -i "fetchmail" /var/log/odoo/odoo.log | tail -20

# Check mail routing
sudo grep -i "mail.*route\|routing" /var/log/odoo/odoo.log | tail -20

# Check for errors
sudo grep -i "error\|failed" /var/log/odoo/odoo.log | tail -50

Models That Support Email Routing

Any model that inherits mail.thread supports email routing:

Model Description
purchase.order Purchase Orders
sale.order Sales Orders
crm.lead CRM Leads/Opportunities
helpdesk.ticket Helpdesk Tickets
project.task Project Tasks
account.move Invoices/Bills
hr.applicant Job Applications

Example Model Definition

from odoo import models

class PurchaseOrder(models.Model):
    _name = 'purchase.order'
    _inherit = ['mail.thread', 'mail.activity.mixin']

    # The model now supports:
    # - Chatter (message log)
    # - Email sending
    # - Email routing (incoming)
    # - Activities

Mail Aliases

Mail aliases allow routing emails to create new records or find existing ones.

View Aliases

Navigate to: Settings → Technical → Email → Aliases

Alias Model Description
sales sale.order Create new quotation
purchase purchase.order Create new RFQ
support helpdesk.ticket Create new ticket

How Aliases Work

When an email arrives at support@yourdomain.com:

  1. Fetchmail downloads the email
  2. Odoo parses the To address: support@yourdomain.com
  3. Finds mail.alias with alias_name = 'support'
  4. Creates new helpdesk.ticket or routes to existing one

Required Modules

Module Purpose
mail Core mail functionality, mail.thread
fetchmail Incoming mail fetching
Model-specific e.g., purchase, sale, helpdesk

Check Installed Modules

SELECT name, state
FROM ir_module_module
WHERE name IN ('mail', 'fetchmail', 'purchase', 'sale')
ORDER BY name;

Best Practices

  1. Use IMAP over POP3 - IMAP allows Odoo to mark emails as read without deleting them

  2. Set reasonable fetch interval - 5 minutes is usually sufficient, lower intervals increase server load

  3. Use dedicated email account - Create a separate email for Odoo (e.g., odoo@yourdomain.com) rather than using personal email

  4. Monitor fetchmail logs - Regularly check logs for connection issues

  5. Test after updates - Verify fetchmail cron is still active after Odoo updates

  6. Use app passwords - For Gmail/Office 365, use app-specific passwords instead of main password

Gmail/Google Workspace Setup

For Gmail, you need:

  1. Enable "Less secure app access" OR use App Password
  2. Enable IMAP in Gmail settings
  3. Use these settings:
Setting Value
Server imap.gmail.com
Port 993
SSL Yes
Username your-email@gmail.com
Password App Password (16 characters)

Office 365 Setup

For Office 365:

Setting Value
Server outlook.office365.com
Port 993
SSL Yes
Username your-email@yourdomain.com
Password App Password

OAuth2

Microsoft is deprecating basic auth. You may need OAuth2 authentication module for Office 365.