Technical Deep Dive: How CRM Outreach Campaigns Works Under the Hood
CRM Outreach Campaigns is a server-side Python module for Odoo Community Edition. No JavaScript frontend, no external dependencies, no API calls to third-party services. This article explains the architecture — data model, email sending flow, reply detection, Do Not Contact cascade, and the design decisions behind it.
This is not a marketing page. It's a technical walkthrough for people who want to evaluate the module before installation or understand the architecture after installation.
Data Model: Five Objects, One Flow
The module adds 5 new models to Odoo and extends the existing CRM lead model.
crm.outreach.sender — Sender Email
Represents a sender identity: display name, email address, signature (HTML), and a link to an ir.mail_server (outgoing SMTP). One sender per sales rep. The module sends emails through the sender's SMTP server, not the system default.
Key fields: name, email, signature, mail_server_id
crm.outreach.campaign — Campaign
A named container for an email sequence. Has a One2many relationship with campaign steps (email templates). Tracks overall campaign status and links to all assigned leads.
Key fields: name, step_ids (One2many), lead_ids (One2many)
crm.outreach.step — Campaign Step
One email in a sequence. Defines the email subject, body template (with placeholders), and the delay in days from the previous step. Steps are ordered by sequence number within a campaign.
Key fields: campaign_id, sequence, subject, body_template, delay_days
crm.outreach.lead — Campaign Lead (Junction)
The junction model between campaigns and CRM leads. This is where per-lead tracking happens. Each record tracks: which campaign, which lead, which step the lead is currently on, the lead's status (New, Contacted, Replied, Positive, Not Now, Dead), and the next email due date.
Key fields: campaign_id, lead_id, current_step_id, status, next_send_date, sender_id
crm.lead (extended) — CRM Lead Extension
The standard CRM lead model is extended with a "Do Not Contact" flag and a reverse link to all campaign assignments. The DNC flag is also added to res.partner (contacts and companies) for cascade blocking.
Added fields: do_not_contact, do_not_contact_reason, do_not_contact_date, outreach_lead_ids
Email Sending Flow
The module does not send emails automatically. This is a deliberate design decision. Here's the flow:
1. Lead is assigned to a campaign
A crm.outreach.lead record is created. Status = "New". The next_send_date is calculated based on the first step's delay (usually 0 = today).
2. Rep opens their work queue
The dashboard shows all leads where next_send_date <= today and status is "New" or "Contacted". These are the emails due for today.
3. Rep reviews and sends each email
The template is rendered with the lead's data (contact name, company, etc.) and presented for review. The rep can edit before sending. The email goes through the sender's SMTP server, not the system default.
4. Status and next step are updated
After sending, the lead's status moves to "Contacted", the current step advances, and the next_send_date is recalculated based on the next step's delay.
5. Sequence completes or is interrupted
If the lead replies, gets marked as "Not Now", or reaches the last step, the sequence stops for that lead. No more emails are scheduled.
Why Not Automatic Sending?
Many email outreach tools send on autopilot. We deliberately chose a review-then-send model:
Quality control: Reps see each email before it goes out. They can catch placeholder failures (missing company name, wrong contact), add a personal note, or skip a lead that doesn't fit.
Deliverability safety: Automatic sending of hundreds of emails through a standard Odoo SMTP setup can trigger spam filters. Manual sending at a human pace (30–50/day) keeps deliverability rates high.
Compliance awareness: Human review is the last line of defense against emailing someone who shouldn't be contacted. The rep sees the email and the lead context before clicking send.
No cron dependency: Automatic sending requires reliable cron jobs, which can fail silently. Manual sending means the rep knows whether emails went out or not — no silent failures.
Reply Detection: How Replies Are Recognized
Reply detection uses Odoo's built-in incoming mail server. When the module sends an email, it sets the Message-ID and Reply-To headers appropriately. When a reply arrives, Odoo's mail gateway matches it to the original message via the In-Reply-To header.
The module hooks into this process: when a reply is matched to a lead that's part of an active campaign, the lead's campaign status automatically changes to "Replied". The reply content appears in the lead's chatter thread.
Requirement: Reply detection requires an incoming mail server (IMAP or POP3) configured in Odoo. Without it, emails will still be sent, but replies won't be detected automatically — the rep would need to update the status manually.
Do Not Contact Cascade
The DNC system operates at three levels: lead, contact (partner), and company (parent partner). When DNC is set at a higher level, it cascades down:
Lead-level DNC: Only this specific lead is blocked. Other leads with the same contact remain active. Useful for leads that are clearly irrelevant (wrong department, wrong product fit).
Contact-level DNC: The contact (res.partner) is flagged. All CRM leads linked to this contact are blocked from outreach. New leads created for this contact will also be blocked.
Company-level DNC: The company (parent res.partner) is flagged. All child contacts inherit the block. All leads linked to any contact at this company are blocked. This is the "stop contacting this organization" button.
Every DNC action records a reason and timestamp for audit purposes. The cascade is enforced both in the UI (leads are visually marked as blocked) and in the send logic (the module checks DNC status before allowing an email to be sent).
Template Rendering
Template rendering happens at send time, not at assignment time. When the rep opens an email for review, the module resolves all {{ placeholder }} variables by pulling data from three sources:
- Lead: contact_name, email, phone, mobile, company_name, street, city, zip, country, website, job_title, first_name
- Sender: sender_name, sender_email, signature
- Context: today, current_day
If a placeholder can't be resolved (e.g., the lead has no company name), it renders as an empty string. The rep sees the final email before sending and can fill in any gaps manually.
What the Module Does Not Do
Transparency about scope is important for technical evaluation:
- No automatic email sending — emails are reviewed and sent manually by the rep
- No email warm-up — deliverability management is outside the module's scope
- No open/click tracking — the module doesn't inject tracking pixels or redirect links
- No A/B testing — template variants are managed manually by creating separate campaigns
- No JavaScript frontend — all views use standard Odoo server-side rendering
Conclusion
CRM Outreach Campaigns is a focused module: five models, one clear flow, no external dependencies. It does one thing — structured email sequences in the CRM — and does it cleanly. No attempt to be a full marketing automation system.
For technical evaluators: the code is server-side Python, standard Odoo ORM, no hacks. Installable on Odoo 16–19, no conflicts with the core CRM module.
The CRM Outreach Campaigns module is available on the Odoo App Store. Recommendation: install on a staging database to review the code and test the workflow before deploying to production.
Related articles:


