This article is currently available in English only. Translation coming soon.
By the end of this recipe, your Odoo 19 contacts will sync to a Mailchimp audience with merge fields preserved, segments built from Odoo data (last purchase date, lifetime value, tag), Mailchimp opens and clicks pushed back to Odoo CRM as activities, and full GDPR-compliant unsubscribe handling. Skill required: Odoo developer with REST API basics. Time required: 3 hours setup, 90 minutes testing. ECOSIRE has built this for B2B clients running drip campaigns and B2C clients running 50,000-recipient newsletters, and the recipe below is the playbook we ship.
The trap most teams fall into: bidirectional sync with no master. A contact updated in Odoo overwrites a Mailchimp-side update from a customer who unsubscribed via the email footer. Now Odoo thinks they're subscribed but Mailchimp refuses to email them. The recipe below makes Mailchimp the master for subscription state and Odoo the master for everything else.
What you will need
- Mailchimp account with at least the Standard plan (Free plan blocks API access on most automation features).
- Mailchimp API key: User > Account > Extras > API keys > Create.
- Mailchimp audience (list) ID: Audience > Settings > Audience name and defaults > Audience ID (looks like
1a2b3c4d5e). - Odoo version: 17, 18, or 19. The OCA
mail_mailchimpmodule is a starting point; for production we typically extend it. - Webhook endpoint on your Odoo (HTTPS).
- Time: 3 hours setup, 90 minutes testing.
Step-by-step
1. Configure the Mailchimp backend in Odoo
Add a config record:
class MailchimpAccount(models.Model):
_name = 'mailchimp.account'
name = fields.Char()
api_key = fields.Char() # format: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-us21
audience_id = fields.Char()
server_prefix = fields.Char(compute='_compute_server_prefix')
@api.depends('api_key')
def _compute_server_prefix(self):
for r in self:
r.server_prefix = r.api_key.split('-')[-1] if r.api_key else 'us1'
def _api_url(self, path):
return f"https://{self.server_prefix}.api.mailchimp.com/3.0{path}"
def _request(self, method, path, **kwargs):
return requests.request(method, self._api_url(path),
auth=('anystring', self.api_key), timeout=30, **kwargs)
The server prefix (us1, us21, etc.) is the suffix of the API key. Verification: a _request('GET', '/ping') returns Everything's Chimpy.
2. Push contacts from Odoo to Mailchimp
Map Odoo fields to Mailchimp merge fields:
| Odoo field | Mailchimp merge tag |
|---|---|
| name | FNAME LNAME (split) |
| (the email address itself) | |
| country_id.name | COUNTRY |
| total_spent | LTV |
| last_order_date | LASTORDER |
| customer_rank | TIER |
def _upsert_member(self, partner):
if not partner.email:
return
email_hash = hashlib.md5(partner.email.lower().encode()).hexdigest()
fname, _, lname = (partner.name or '').partition(' ')
payload = {
'email_address': partner.email,
'status_if_new': 'subscribed' if partner.email_optin else 'unsubscribed',
'merge_fields': {
'FNAME': fname,
'LNAME': lname,
'COUNTRY': partner.country_id.name or '',
'LTV': partner.total_spent or 0,
'TIER': partner.customer_rank or 0,
},
'tags': partner.category_id.mapped('name'),
}
self._request('PUT', f"/lists/{self.audience_id}/members/{email_hash}", json=payload)
Trigger this on res.partner write/create. Verification: edit a partner email in Odoo; Mailchimp audience refreshes within 5 seconds.
3. Configure webhook for Mailchimp > Odoo
Mailchimp can POST to your Odoo when a member subscribes, unsubscribes, profile updates, or cleans (bounces). In Mailchimp > Audience > Settings > Webhooks, set URL = https://your-odoo.com/mailchimp/webhook, tick all event types.
@http.route('/mailchimp/webhook', auth='public', csrf=False, methods=['POST', 'GET'])
def webhook(self, **kwargs):
if request.httprequest.method == 'GET':
return request.make_response('OK', status=200) # Mailchimp's verification ping
event_type = kwargs.get('type')
data = kwargs.get('data', {})
email = data.get('email')
partner = request.env['res.partner'].sudo().search([('email', '=ilike', email)], limit=1)
if not partner:
return request.make_response('Not found', status=200) # 200 not 404 - don't trigger Mailchimp retries
if event_type == 'unsubscribe':
partner.email_optin = False
elif event_type == 'subscribe':
partner.email_optin = True
elif event_type == 'cleaned':
partner.email_invalid = True
return request.make_response('OK', status=200)
Verification: unsubscribe from a Mailchimp campaign using the footer link; the Odoo partner record flips email_optin=False within 30 seconds.
4. Build segments based on Odoo data
In Mailchimp > Audience > Segments, build dynamic segments using the merge fields you populated:
- VIP customers:
LTV > 5000 AND TIER >= 2 - At-risk customers:
LASTORDER < 90 days ago AND LTV > 1000 - New US signups:
COUNTRY = "United States" AND created_at < 30 days ago
These segments auto-refresh as Odoo pushes updates. Verification: a contact moves from one segment to another when their underlying Odoo data changes.
5. Trigger campaigns from Odoo events
Use the Mailchimp automations API to start an automation customer journey from an Odoo event:
def _trigger_journey(self, automation_id, member_email):
self._request('POST',
f"/automations/{automation_id}/emails/{email_id}/queue",
json={'email_address': member_email}
)
Wire to Odoo automated actions: when a customer abandons a cart, queue them into the "abandoned cart" automation. When they make their first purchase, queue them into the "welcome series".
Verification: a test customer trips an automation and receives the first email within 1 to 5 minutes.
6. Pull opens and clicks back to Odoo
For attribution, pull engagement events from Mailchimp's reports API and post them to the matching CRM lead/opportunity:
def _pull_campaign_engagement(self, campaign_id):
resp = self._request('GET', f"/reports/{campaign_id}/email-activity?count=1000")
for member in resp.json().get('emails', []):
partner = self.env['res.partner'].sudo().search([
('email', '=ilike', member['email_address']),
], limit=1)
if not partner:
continue
for activity in member.get('activity', []):
if activity['action'] in ('open', 'click'):
lead = self.env['crm.lead'].search([
('partner_id', '=', partner.id),
('stage_id.is_won', '=', False),
], limit=1)
if lead:
lead.message_post(body=f"Mailchimp {activity['action']}: {activity.get('url') or campaign_id}")
Schedule daily. Verification: a recipient who clicks a campaign link sees that activity logged on their CRM lead.
7. Handle merge field updates dynamically
When Odoo schema changes (e.g., a new computed field on res.partner), update the Mailchimp merge fields list:
def _ensure_merge_fields(self):
expected = {'FNAME', 'LNAME', 'COUNTRY', 'LTV', 'TIER', 'LASTORDER'}
resp = self._request('GET', f"/lists/{self.audience_id}/merge-fields?count=100")
existing = {f['tag'] for f in resp.json()['merge_fields']}
for tag in expected - existing:
self._request('POST', f"/lists/{self.audience_id}/merge-fields", json={
'tag': tag,
'name': tag.title(),
'type': 'number' if tag in ('LTV', 'TIER') else 'text',
})
Run on module install/upgrade. Verification: required merge fields exist on the Mailchimp side.
8. GDPR compliance: double opt-in and audit trail
For EU contacts, use double opt-in: status_if_new = pending (not subscribed). Mailchimp sends a confirmation email; the contact must click before Mailchimp marks them subscribed.
status = 'pending' if partner.country_id.code in EU_COUNTRIES else 'subscribed'
Keep an audit log on the Odoo side showing every opt-in source (form, import, manual) with timestamp.
Verification: a new EU contact is created with status pending; they receive the confirm email and click; status moves to subscribed; Odoo log shows the source.
Common mistakes
- Bidirectional sync without a master. Pick: Mailchimp masters subscription, Odoo masters everything else.
- Pushing without checking opt-in. GDPR fines start at €20 million.
- Ignoring
cleanedandbouncedevents. Hard bounces to a dead address tank your sender reputation. - Hardcoding the API server prefix. Different keys are on different DCs; always parse from the key suffix.
- Importing a CSV of 50,000 contacts to Mailchimp without warmup. Mailchimp throttles cold imports; warm by sending small campaigns first.
Going further
Behavioral triggers: track Odoo events like "viewed product 3 times" and trigger highly targeted Mailchimp campaigns. Pair with Odoo website analytics to capture browse behavior and feed it as merge fields.
RFM segmentation: build Recency-Frequency-Monetary segments in Odoo, push the RFM cohort code as a merge field, run a different campaign per cohort. Champions (high R, high F, high M) get loyalty; At-Risk (low R, mid F, mid M) get win-back.
Multi-store / multi-brand: each brand gets its own Mailchimp audience; the connector pushes to the right audience based on partner.brand_id. ECOSIRE itself runs three brand audiences (ECOSIRE, Odovation, Muhammad Amir).
Postcards: Mailchimp also supports physical postcards via the same API. Trigger one when a high-value customer hits 90-day inactivity. Physical mail has 5x higher response rate than email for win-back.
A/B subject line testing: Mailchimp's built-in A/B test runs 10% of the audience on each variant, picks the winner, sends to the rest. Pair with a custom merge field to track which variant a recipient saw.
Predicted send time: Mailchimp ML predicts the best time to send for each contact. Enable in audience settings; open rates lift 10-20% on average.
Drip onboarding sequences: 7-email drip starting at signup. Each email triggers based on time elapsed AND completion of previous email's CTA. Mailchimp's Customer Journeys builder handles this visually.
Post-purchase journey: 5-email sequence after first purchase. Welcome > Product Tips > Cross-sell > Review Request > Loyalty Invite. Each step optional; skip based on customer behavior.
Re-engagement: customers who haven't opened in 90+ days get a "we miss you" campaign. Those who don't respond after 2 attempts get scrubbed (improves overall deliverability).
Sunset policy: hard-bounce or persistent non-engagement triggers automatic unsubscribe to maintain list hygiene. Mailchimp's Compliance section has this built in.
Holiday campaign automation: schedule a 12-month calendar of campaigns (Black Friday, Cyber Monday, Valentine's, etc.). Pair with category/product filters so each campaign features the relevant inventory.
Revenue attribution: tag every Mailchimp campaign with a UTM parameter that flows into the Odoo SO's source field. Compute revenue attributed to each campaign.
SMS + Email omnichannel: Mailchimp Premium includes SMS. Run synchronized campaigns where SMS and email reinforce each other within the same customer journey.
Transactional triggers via Mandrill: for transactional emails (order confirm, password reset), use Mandrill (Mailchimp's transactional service). Same audience, different sender reputation.
For full Mailchimp + Odoo deployment including custom segmentation logic, behavioral triggers, and revenue attribution, ECOSIRE custom Odoo development builds the entire stack. Pair this with how to integrate WhatsApp Business API with Odoo for omnichannel.
Frequently Asked Questions
What does Mailchimp cost?
Free up to 500 contacts. Standard tier ($20/month) up to 1,500 contacts. Premium ($350/month) for 10,000+. Pricing scales with audience size, not message count.
Should I use Mailchimp or Odoo's native Email Marketing?
Odoo's native is great for transactional emails (order confirmations, invoices) and small marketing pushes. Mailchimp wins for large-list deliverability, A/B testing UI, and customer journey automations. We often run both: Odoo for transactional, Mailchimp for marketing.
Can I sync GDPR consent records?
Yes. Mailchimp's marketing_permissions field tracks consent per channel. Push the consent state from Odoo (partner.email_optin_at timestamp) so Mailchimp records the consent date.
How do I handle people who exist in two Odoo records with the same email?
Mailchimp keys off email — only one Odoo record can "own" each email. Build a deduplication script that merges duplicate res.partner records before sync.
For complex Mailchimp deployments including AI subject-line testing, predicted send times, and marketing-attributed pipeline reporting, ECOSIRE marketing services build the analytical layer. Or read how to integrate DocuSign with Odoo to round out the customer-journey toolkit.
تحریر
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Odoo ERP کے ساتھ اپنے کاروبار کو تبدیل کریں
آپ کے کاموں کو ہموار کرنے کے لیے ماہر Odoo کا نفاذ، حسب ضرورت، اور معاونت۔
متعلقہ مضامین
How to Add a Custom Button to an Odoo Form View (2026)
Add custom action buttons to Odoo 19 form views: Python action method, view inheritance, conditional visibility, confirmation dialogs. Production-tested.
How to Add a Custom Field in Odoo Without Studio (2026)
Add custom fields via custom module in Odoo 19: model inheritance, view extension, computed fields, store/non-store decisions. Code-first, version-controlled.
How to Add a Custom Report in Odoo Using External Layout
Build a branded PDF report in Odoo 19 using web.external_layout: QWeb template, paperformat, action binding. With print logo + footer overrides.