{"id":11220,"date":"2026-06-03T14:24:00","date_gmt":"2026-06-03T08:54:00","guid":{"rendered":"https:\/\/tenthplanet.in\/blogs\/?p=11220"},"modified":"2026-06-05T18:06:17","modified_gmt":"2026-06-05T12:36:17","slug":"odoo-contacts-vs-mailing-list-contacts-stop-duplicates","status":"publish","type":"post","link":"https:\/\/tenthplanet.in\/blogs\/odoo-contacts-vs-mailing-list-contacts-stop-duplicates\/","title":{"rendered":"Odoo Contacts vs Mailing Lists: Stop Duplicate Emails Guide"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><strong>The problem in one sentence<\/strong><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">If you run Odoo and your customers receive the same marketing email twice, or your newsletter signups never make it into your customer database, you&#8217;re not doing anything wrong \u2014 Odoo stores contacts in <strong>two separate tables that don&#8217;t talk to each other by default<\/strong>. This guide shows you exactly how to fix that, end the duplicates, and set up your Contacts, Mailing Lists, and Campaigns the way Odoo intended.<\/p>\n<\/blockquote>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"538\" src=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/Odoo_Email_marketing-1024x538.png\" alt=\"\" class=\"wp-image-11247\" srcset=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/Odoo_Email_marketing-1024x538.png 1024w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/Odoo_Email_marketing-300x158.png 300w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/Odoo_Email_marketing-768x403.png 768w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/Odoo_Email_marketing-1536x807.png 1536w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/Odoo_Email_marketing.png 1731w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">The Five-Minute Answer<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Odoo has <strong>two completely separate contact tables<\/strong>: res.partner (Contacts) and mailing.contact (Email Marketing). They are not automatically linked.<\/li>\n\n\n\n<li>Use res.partner as your <strong>single source of truth<\/strong>. Target it directly in Email Marketing by setting <strong>Recipients = Contact<\/strong> instead of Mailing List.<\/li>\n\n\n\n<li>Reserve mailing.contact only for <strong>anonymous newsletter subscribers<\/strong> who haven&#8217;t yet become customers \u2014 or skip it entirely and capture sign-ups as CRM leads.<\/li>\n\n\n\n<li>Set <strong>&#8220;Unicity based on = Email&#8221;<\/strong> on every Marketing Automation campaign to prevent duplicate enrollments.<\/li>\n\n\n\n<li>Use the <strong>global blacklist<\/strong> for unsubscribes and per-list <strong>opt-out<\/strong> for topic preferences. Transactional emails (orders, invoices, tickets) still go through.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Also Read <a href=\"https:\/\/tenthplanet.in\/blogs\/link-odoo-contacts-to-mailing-lists-data-consent-setup\/\" data-type=\"link\" data-id=\"https:\/\/tenthplanet.in\/blogs\/link-odoo-contacts-to-mailing-lists-data-consent-setup\/\">Version 2<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. Why Odoo Has Two Contact Tables (And Why It Confuses Everyone)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Almost every Odoo user discovers this the hard way: they create a beautiful customer record in the <strong>Contacts<\/strong> app, build a campaign in <strong>Email Marketing<\/strong>, hit send, and then realise the customer never got the email \u2014 because the campaign was sent to a <strong>Mailing List<\/strong> that the customer was never added to.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This isn&#8217;t a bug. It&#8217;s how Odoo&#8217;s data model is structured. Once you understand <em>why<\/em> it&#8217;s structured that way, the fix becomes obvious.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Two Tables Explained<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>res.partner<\/strong> is Odoo&#8217;s master contact table. Every customer, vendor, employee, lead, and child address lives here. It&#8217;s the central record used by Sales, Invoicing, CRM, Field Service, Purchase, Inventory, Helpdesk, and the Website. When you create a sales order, the customer on that order <em>is<\/em> a res.partner.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>mailing.contact<\/strong> is a completely separate table that exists only inside the Email Marketing module. It has its own name, email, and subscription preferences. It has <strong>no foreign key<\/strong> to res. partner. The same human being can exist in both tables simultaneously, and Odoo will treat them as two different people.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"780\" height=\"467\" src=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image.png\" alt=\"\" class=\"wp-image-11222\" srcset=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image.png 780w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-300x180.png 300w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-768x460.png 768w\" sizes=\"auto, (max-width: 780px) 100vw, 780px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Why Odoo Designed It This Way<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The separation made historical sense. Newsletter subscribers from a public website don&#8217;t need a billing address, a tax ID, or a customer rank \u2014 they just need an email and a few preferences. Forcing every random visitor into the main customer table would bloat res.partner with thousands of low-value records.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The problem is that Odoo never built an <strong>automatic bridge<\/strong> between the two for when a subscriber later becomes a customer. That bridge is what you need to build (or sidestep) yourself.<br><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. Operational Emails vs Marketing Emails: Know the Difference<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before you fix anything, you need to classify every email your business sends into one of two buckets. Treating them the same is the second-most-common mistake (after not realising the two tables are separate).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Operational Emails (Transactional)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">These are triggered by <strong>business events<\/strong> \u2014 a sale, an invoice, a delivery, a support ticket. The customer expects them. They have a legal basis under &#8220;contract performance&#8221; or &#8220;legitimate interest&#8221; (GDPR Art. 6(1)(b) and (f)). They are sent via Odoo&#8217;s mail.message \/ mail.mail pipeline and <strong>ignore the marketing blacklist<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Typical examples in any business:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Order confirmation (Sales)<\/li>\n\n\n\n<li>Invoice notification (Accounting)<\/li>\n\n\n\n<li>Payment receipt (Accounting)<\/li>\n\n\n\n<li>Delivery \/ shipping notification (Inventory)<\/li>\n\n\n\n<li>Appointment confirmation &amp; reminder (Appointments)<\/li>\n\n\n\n<li>Field Service \/ installation scheduled &amp; completed<\/li>\n\n\n\n<li>Support ticket acknowledged &amp; resolved (Helpdesk)<\/li>\n\n\n\n<li>Overdue payment reminder (Follow-up levels)<\/li>\n\n\n\n<li>Portal account invitation<\/li>\n\n\n\n<li>Quotation sent<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Marketing Emails<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">These exist to <strong>nurture, sell, or re-engage<\/strong>. They require active consent (GDPR Art. 6(1)(a)). They are sent through the Email Marketing or Marketing Automation modules and <strong>respect the blacklist and per-list opt-outs<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Typical examples:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Welcome email after first purchase<\/li>\n\n\n\n<li>Post-purchase drip series (educational tips)<\/li>\n\n\n\n<li>Feedback &amp; review requests<\/li>\n\n\n\n<li>Cross-sell &amp; upsell campaigns<\/li>\n\n\n\n<li>Abandoned cart recovery<\/li>\n\n\n\n<li>Win-back for inactive customers<\/li>\n\n\n\n<li>Monthly newsletter<\/li>\n\n\n\n<li>Promotional &amp; seasonal offers<\/li>\n\n\n\n<li>Referral program invitations<\/li>\n\n\n\n<li>Anniversary &amp; loyalty messages<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"742\" height=\"289\" src=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-2.png\" alt=\"\" class=\"wp-image-11226\" srcset=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-2.png 742w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-2-300x117.png 300w\" sizes=\"auto, (max-width: 742px) 100vw, 742px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Problem A: Duplicate res.partner Records With the Same Email<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Odoo does not enforce a unique email on contacts. The same address can legitimately end up on multiple res.partner rows because of:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Child address records:<\/strong> a customer&#8217;s separate billing or delivery address is its own partner row, often inheriting the parent&#8217;s email.<\/li>\n\n\n\n<li><strong>Website checkout<\/strong> creating a new contact instead of matching an existing one.<\/li>\n\n\n\n<li><strong>CSV imports<\/strong> not merging on email.<\/li>\n\n\n\n<li><strong>Manual re-creation<\/strong> by staff who didn&#8217;t search before adding.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">When you send a mailing targeted at &#8220;Contact&#8221;, each row counts as a separate recipient \u2014 so the same person gets emailed multiple times.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Problem B: The Same Person Lives in Both Tables<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A typical flow:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Visitor signs up for the newsletter on your website \u2192 a mailing.contact is created.<\/li>\n\n\n\n<li>Six months later, the same visitor buys something \u2192 a res.partner is created.<\/li>\n\n\n\n<li>Now they exist in both tables with no link.<\/li>\n\n\n\n<li>Your monthly newsletter goes to the mailing list (hits them once). Your &#8220;thank you for being a customer&#8221; campaign goes to res.partner (hits them again).<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Problem C: One Person Subscribed to Multiple Lists or Mailings<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This one is more nuanced. Odoo actually <strong>does<\/strong> deduplicate inside a single mailing \u2014 if a person is on List A and List B and both are selected in <em>one<\/em> mailing, they only get the email once. But if you send <em>three separate mailings<\/em> on Monday, Tuesday, and Wednesday, each to a different list, the same person gets all three.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>GDPR Compliance Matrix<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Email Type<\/strong><\/td><td><strong>GDPR Basis<\/strong><\/td><td><strong>Consent Required<\/strong><\/td><td><strong>Opt-Out Required<\/strong><\/td><td><strong>Recommended Odoo Mechanism<\/strong><\/td><\/tr><\/thead><tbody><tr><td><strong>Operational<\/strong><\/td><td>Contract \u2014 Art. 6(1)(b) (or legitimate interest 6(1)(f))<\/td><td>No<\/td><td>No<\/td><td>Mail Templates + Automated Actions \u2192 res.partner<\/td><\/tr><tr><td><strong>Service Reminder (pure)<\/strong><\/td><td>Legitimate interest \u2014 Art. 6(1)(f)<\/td><td>No<\/td><td>Recommended (good practice + Art. 21 objection)<\/td><td>Scheduled Actions + Templates \u2192 res.partner<\/td><\/tr><tr><td><strong>Service + Offer (mixed)<\/strong><\/td><td>Consent \/ ePrivacy soft opt-in<\/td><td>Yes (or soft opt-in)<\/td><td>Yes \u2014 unsubscribe mandatory<\/td><td>Email Marketing \/ Automation \u2192 Contact<\/td><\/tr><tr><td><strong>Promotional<\/strong><\/td><td>Consent \u2014 Art. 6(1)(a) + ePrivacy Reg.13<\/td><td>Yes (or soft opt-in)<\/td><td>Yes \u2014 unsubscribe mandatory<\/td><td>Email Marketing \/ Automation \u2192 Contact \/ mailing list<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><br><br><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. The Right Way to Set Up Contacts, Lists &amp; Campaigns<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here is the architecture that works for 95% of businesses. It avoids all three duplicate problems by design rather than patching them after they occur.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1: Designate res.partner as Your Single Source of Truth<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Every paying customer, every CRM lead, every contact form submission, every appointment booking \u2014 all of these live in res.partner. This is non-negotiable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For marketing, when you create a new mailing in Email Marketing, change the <strong>Recipients<\/strong> dropdown from &#8220;Mailing List&#8221; to <strong>&#8220;Contact&#8221;<\/strong>. This targets res.partner directly with full filter power.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"683\" src=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-9-1024x683.png\" alt=\"\" class=\"wp-image-11249\" srcset=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-9-1024x683.png 1024w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-9-300x200.png 300w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-9-768x512.png 768w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-9.png 1536w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2: Apply Filters to Exclude Sub-Address Duplicates<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Most duplicate sends come from <strong>child address records<\/strong> (delivery addresses, invoice addresses) that share the parent&#8217;s email. Always filter them out:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"780\" height=\"173\" src=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-3.png\" alt=\"\" class=\"wp-image-11229\" srcset=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-3.png 780w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-3-300x67.png 300w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-3-768x170.png 768w\" sizes=\"auto, (max-width: 780px) 100vw, 780px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This single filter eliminates the most common cause of &#8220;why did Jane get the email twice?&#8221; because the duplicate was almost always a child record under her main contact.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3: Use Marketing Automation for Lifecycle Campaigns<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For any automated email sequence (welcome series, post-purchase drip, win-back, cross-sell), use <strong>Marketing Automation<\/strong> \u2014 not regular Email Marketing. It has a feature that Email Marketing lacks:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Set &#8220;Unicity based on = Email&#8221;<\/strong> on every Marketing Automation campaign. This ensures that even if a customer has two res.partner records, they only enter the campaign once.<\/p>\n<\/blockquote>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"619\" height=\"428\" src=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-10.png\" alt=\"\" class=\"wp-image-11250\" srcset=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-10.png 619w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-10-300x207.png 300w\" sizes=\"auto, (max-width: 619px) 100vw, 619px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Step 4: Decide How You Handle Anonymous Newsletter Subscribers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You have two valid options. Pick one:<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Option A (Recommended): Skip mailing.contact entirely<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Replace the website Newsletter snippet with a <strong>Form<\/strong> building block that creates a CRM <strong>Lead\/Opportunity<\/strong> instead of a mailing.contact. Now every signup is a first-class record from day one. Market to them via Email Marketing with <strong>Recipients = Lead\/Opportunity<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Why this is better:<\/strong> no mailing.contact records are ever created, so Problem B literally cannot occur.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Option B: Keep mailing.contact, opt them out on conversion<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">If you keep the Newsletter snippet, create an <strong>Automation Rule<\/strong> that opts the matching mailing.contact out of its lists whenever a corresponding res.partner is created.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Automation Rule \u2014 Settings \u2192 Technical \u2192 Automation Rules Model: Contact (res.partner)<br>Trigger: On Creation<br>Action: Execute Code<\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code># Opt out matching mailing.contact when a new partner is created\nfor partner in records:\n    if not partner.email:\n        continue\n    norm = partner.email.strip().lower()\n    mcontacts = env&#091;'mailing.contact'].search(&#091;('email', '=ilike', norm)])\n    for mc in mcontacts:\n        # Verify the subscription field name in Settings \u2192 Technical \u2192 Models\n        for sub in mc.subscription_ids:\n            sub.opt_out = True<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"780\" height=\"135\" src=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-5.png\" alt=\"\" class=\"wp-image-11231\" srcset=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-5.png 780w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-5-300x52.png 300w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-5-768x133.png 768w\" sizes=\"auto, (max-width: 780px) 100vw, 780px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Step 5: Configure Two SMTP Servers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This is one of the highest-impact configurations most Odoo users skip. Marketing campaigns occasionally trigger spam complaints; if they go through the same server as your invoices and order confirmations, your transactional deliverability suffers.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"742\" height=\"237\" src=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-6.png\" alt=\"\" class=\"wp-image-11232\" srcset=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-6.png 742w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-6-300x96.png 300w\" sizes=\"auto, (max-width: 742px) 100vw, 742px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Configure both in Settings \u2192 Technical \u2192 Outgoing Mail Servers. Set the transactional server to Priority 1 (lower number wins) and the marketing server to Priority 2. Then enable the dedicated marketing server in Email Marketing \u2192 Configuration \u2192 Settings \u2192 Dedicated Server.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5. A Customer Onboarding Workflow That Doesn&#8217;t Create Duplicates<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here is the eight-stage flow we recommend for any service-or-product business running Odoo. Every stage maps to a specific module and produces clean, non-duplicated records.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"742\" height=\"474\" src=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-7.png\" alt=\"\" class=\"wp-image-11234\" srcset=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-7.png 742w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-7-300x192.png 300w\" sizes=\"auto, (max-width: 742px) 100vw, 742px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">The Critical Step: Capture Data at Service\/Installation Time<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Step 6 is where most businesses lose the chance to automate their lifecycle marketing. When a service is completed or a product installed, your technician or fulfillment team must set custom fields on the res.partner record. These fields then power every downstream automation:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><th>Custom Field<\/th><th>Type<\/th><th>Used By<\/th><\/tr><tr><td>x_install_date<\/td><td>Date<\/td><td>Anniversary email, annual service reminder, warranty expiry, post-install drip timing<\/td><\/tr><tr><td>x_next_service_date<\/td><td>Date<\/td><td>Maintenance reminder, reorder prompts<\/td><\/tr><tr><td>x_warranty_end<\/td><td>Date<\/td><td>Warranty expiry notification, upgrade campaigns<\/td><\/tr><tr><td>x_product_type<\/td><td>Many2many tags<\/td><td>Cross-sell segmentation, product-specific tips<\/td><\/tr><tr><td>x_account_type<\/td><td>Selection<\/td><td>B2B vs B2C messaging, tone, channel<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Add these fields via Odoo Studio or Settings \u2192 Technical \u2192 Fields (developer mode required for the latter).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6. Unsubscribe &amp; Blacklist: Keep Marketing Compliant, Keep Ops Flowing<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The most common misconception: &#8220;If a customer unsubscribes, will they stop getting their invoices?&#8221; <strong>No.<\/strong> Odoo handles this correctly out of the box. Here&#8217;s exactly how it works.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Two Levels of Unsubscribe<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Level 1 \u2014 Per-list Opt-Out (preferred for topic preferences)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When you create a mailing list, enable the <strong>&#8220;Show in Preferences&#8221;<\/strong> toggle. This makes the list visible on your unsubscribe page. Customers can then toggle individual lists on or off without leaving everything.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"832\" height=\"300\" src=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-11.png\" alt=\"\" class=\"wp-image-11255\" srcset=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-11.png 832w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-11-300x108.png 300w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-11-768x277.png 768w\" sizes=\"auto, (max-width: 832px) 100vw, 832px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Level 2 \u2014 Global Blacklist (the nuclear option)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the customer clicks &#8220;Unsubscribe from all marketing&#8221;, their email goes into mail.blacklist. From that point on, <strong>every marketing email<\/strong> across <strong>every mailing list<\/strong> and <strong>every Marketing Automation campaign<\/strong> is blocked at delivery \u2014 but <strong>transactional emails still go through<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Enable this option in Email Marketing \u2192 Configuration \u2192 Settings \u2192 Blacklist Option when Unsubscribing.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Recommended Mailing List Structure<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Almost every business should have these four lists (regardless of industry):<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><th>List Name<\/th><th>Purpose<\/th><th>Show in Preferences<\/th><\/tr><tr><td>Newsletter<\/td><td>Monthly updates, blog roundups, education<\/td><td>\u2713<\/td><\/tr><tr><td>Promotions &amp; Offers<\/td><td>Seasonal deals, discounts, sales<\/td><td>\u2713<\/td><\/tr><tr><td>Product Tips &amp; Care<\/td><td>How-to content, maintenance, best practices<\/td><td>\u2713<\/td><\/tr><tr><td>Service Reminders<\/td><td>Renewal, maintenance, reorder prompts<\/td><td>\u2713 (clearly labelled)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"780\" height=\"135\" src=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-8.png\" alt=\"\" class=\"wp-image-11239\" srcset=\"https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-8.png 780w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-8-300x52.png 300w, https:\/\/tenthplanet.in\/blogs\/wp-content\/uploads\/sites\/21\/2026\/06\/image-8-768x133.png 768w\" sizes=\"auto, (max-width: 780px) 100vw, 780px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">The Grey Zone: Service-Critical Reminders<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Some emails sit awkwardly between operational and marketing. A filter replacement reminder is technically a marketing email under Odoo&#8217;s default routing, but most customers consider it part of the service they paid for.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fix: for service-critical reminders, use a <strong>Scheduled Action<\/strong> with a server-action email template (not Marketing Automation). Scheduled actions send via the transactional pipeline and bypass the blacklist, so even an opted-out customer gets their service reminder.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">7. Monthly Health Check: Keep Your Database Clean<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Even with perfect setup, duplicates creep in. Run this 15-minute audit on the first Monday of every month.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Five-Point Monthly Checklist<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Run the Deduplicate Contacts wizard.<\/strong> Go to Contacts \u2192 list view \u2192 Action menu \u2192 Merge Contacts. Odoo groups potential duplicates by email and lets you merge in bulk.<\/li>\n\n\n\n<li><strong>Review the blacklist.<\/strong> Check Email Marketing \u2192 Configuration \u2192 Blacklisted Email Addresses for any false positives (e.g., someone who clicked unsubscribe by mistake and now wants back in).<\/li>\n\n\n\n<li><strong>Check bounce rates per campaign.<\/strong> High bounces usually point to imported lists with stale emails \u2014 flag them for cleanup.<\/li>\n\n\n\n<li><strong>Audit mailing list growth vs customer growth.<\/strong> If your newsletter list grew faster than your customer base, you&#8217;re either capturing well \u2014 or you&#8217;re forgetting to convert subscribers to customers (Problem B).<\/li>\n\n\n\n<li><strong>Spot-check Marketing Automation campaign logs.<\/strong> Look for &#8220;skipped \u2014 duplicate&#8221; events. If they&#8217;re frequent, your Unicity settings are working. If they&#8217;re zero across the board, something might be misconfigured.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Final Thoughts<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Odoo&#8217;s contact system is more powerful than most users realise \u2014 it just hides that power behind a confusing two-table architecture and a default setup that doesn&#8217;t connect the pieces. Once you understand that res.partner is your master record and mailing.contact is an optional satellite, every setup decision becomes clearer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The three rules that prevent 95% of all duplicate-email problems:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Target res.partner directly in Email Marketing \u2014 set <strong>Recipients = Contact<\/strong>, not Mailing List.<\/li>\n\n\n\n<li>Set <strong>Unicity = Email<\/strong> on every Marketing Automation campaign.<\/li>\n\n\n\n<li>Either skip mailing.contact entirely (capture signups as CRM leads) or auto-opt-out on conversion.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Add the filter for child address records, set up two SMTP servers, and run the dedup wizard monthly \u2014 and your Odoo email marketing setup will be cleaner than most enterprise CRM deployments.<\/p>\n\n\n\n<h3 class=\"wp-block-heading has-text-align-center\">Need help implementing this in your Odoo instance?<\/h3>\n\n\n\n<p class=\"has-text-align-center wp-block-paragraph\">TenthPlanet&#8217;s Odoo experts can audit your current Contacts, CRM, and Email Marketing setup and deploy the duplicate-prevention rules tailored to your business workflow.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-f5f78b3b wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/tenthplanet.in\/getintouch\/\">Get in touch<\/a><\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex\"><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The problem in one sentence If you run Odoo and your customers receive the same marketing email twice, or your [&hellip;]<\/p>\n","protected":false},"author":12,"featured_media":11247,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[4],"tags":[85,1037,23],"class_list":["post-11220","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odoo","tag-crm","tag-email-marketing","tag-odoo"],"acf":[],"_links":{"self":[{"href":"https:\/\/tenthplanet.in\/blogs\/wp-json\/wp\/v2\/posts\/11220","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tenthplanet.in\/blogs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tenthplanet.in\/blogs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tenthplanet.in\/blogs\/wp-json\/wp\/v2\/users\/12"}],"replies":[{"embeddable":true,"href":"https:\/\/tenthplanet.in\/blogs\/wp-json\/wp\/v2\/comments?post=11220"}],"version-history":[{"count":20,"href":"https:\/\/tenthplanet.in\/blogs\/wp-json\/wp\/v2\/posts\/11220\/revisions"}],"predecessor-version":[{"id":11282,"href":"https:\/\/tenthplanet.in\/blogs\/wp-json\/wp\/v2\/posts\/11220\/revisions\/11282"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tenthplanet.in\/blogs\/wp-json\/wp\/v2\/media\/11247"}],"wp:attachment":[{"href":"https:\/\/tenthplanet.in\/blogs\/wp-json\/wp\/v2\/media?parent=11220"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tenthplanet.in\/blogs\/wp-json\/wp\/v2\/categories?post=11220"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tenthplanet.in\/blogs\/wp-json\/wp\/v2\/tags?post=11220"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}