Appearance
Are you an LLM? You can read better optimized documentation at /flows/customer/CF-38-mailchimp-newsletter-subscription.md for this page in Markdown format
Mailchimp Newsletter Subscription
Flow ID: CF-38 | Module(s):
ecommercen/eshop/,application/libraries/| Complexity: Medium Last Updated: 2026-06-28
Business Context
This flow is the storefront-facing Mailchimp newsletter signup: a public endpoint (POST /api/mailchimp) that subscribes an email address to a configured Mailchimp audience. It is reached from two storefront surfaces — (a) the homepage/footer newsletter modal, and (b) a server-to-server call from the checkout/order-preview flow when the customer ticks the "subscribe me" checkbox. It is a guest-accessible endpoint (no login required).
Two adjacent concerns live elsewhere and are cross-linked, not re-documented here:
- The Mailchimp Marketing API client (
Adv_Mailchimp_Api), theMAILCHIMP/*registry keys, double-opt-in, and the two-list (site vs checkout) pattern are documented in IN-13 Newsletter Integrations (canonical home for newsletter-provider API details). - The admin configuration page (
settings/mailchimp) is part of AD-13 Settings.
Mailchimp is a parallel newsletter provider alongside Manago/Moosend/Apifon (IN-13); the standalone Adv_mailchimp controller exists so a single subscribe endpoint can serve both the footer modal and the checkout (git d8bcb07ea3, 2024-10-30).
API Reference
REST Endpoints (Modern Layer)
None. /api/mailchimp is a legacy CI route, not part of the modern /rest/v{N}/ stack (no src/Rest controller, no rest_policies.php entry). The modern REST checkout instead fires a MarketingConsentCaptured event that currently has no Mailchimp listener (src/Domains/Checkout/PlaceOrderService.php:261-283, src/Domains/Order/Event/MarketingConsentCaptured.php) — so REST checkout does not subscribe anyone to Mailchimp today.
Legacy Routes
| Route | Maps to | file:line | Auth |
|---|---|---|---|
api/mailchimp | eshop/mailchimp/mailchimpSubscribe | application/config/routes.php:200 | none (public/guest) |
(\w{2})/mailchimpSubscribe | mailchimp/mailchimpSubscribe | routes.php:230 | none (locale-prefixed twin) |
settings/mailchimp | settings/mailchimp | routes.php:199 | admin (ADVISABLE/ADMIN) — see AD-13 |
Request (POST /api/mailchimp, Content-Type: application/json): body { "email": string, "url": string }, read via raw php://input (ecommercen/eshop/controllers/Adv_mailchimp.php:28, getJsonInput() :80-88). The url field routes config: "preview_order" → checkout list/interest/tag; anything else → site list/interest/tag (:34).
Response: {"message":"Successfully subscribed to Mailchimp"} 200 (:46); {"error":"Invalid input: email is missing"} 400 (:31); {"error":"Invalid JSON input"} 400 (:85); {"error":"Subscription failed","details":<msg>} 500 (:49).
Code Flow
Storefront footer modal (newsletter_section_mailchimp.php, gated by MAILCHIMP/ENABLED)
└─ footer_js.php:580-641 (validate email + GDPR consent; derive url from page; POST /api/mailchimp {email,url})
│
Checkout (CheckoutPage.vue:659-675 checkbox name="register_mailchimp_newsletter")
└─ Adv_order.php previewOrder :352-357
└─ if POST register_mailchimp_newsletter && validateMailchimpRegistryValues()
└─ registerCustomerToMailchimp() :1846-1858 (server-to-server cURL POST to site_url('api/mailchimp'), url='preview_order')
│
▼
Adv_mailchimp::mailchimpSubscribe() ecommercen/eshop/controllers/Adv_mailchimp.php:26-51
├─ getJsonInput() :80-88 (JSON_THROW_ON_ERROR)
├─ require email else 400 :30-32
├─ formType = url==='preview_order' ? checkout : site :34
├─ getMailchimpConfig(formType) :59-72 (select checkout/site registry keys)
├─ build payload {email_address,status,interests,tags} :37-42
└─ Mailchimp_Api->addMemberToList(listId, data) -> Mailchimp Marketing API POST /lists/{id}/membersData Model
This flow performs no domain-table reads or writes — all subscriber data is pushed to Mailchimp's remote API and never persisted locally (no subscribers/mailchimp_* table). The only DB touch is configuration in the generic registry key-value table (database/initial/initial.sql:1034-1045), under reggroup='MAILCHIMP'. The registry table and its caching are owned by SY-19 Config & Registry; the MAILCHIMP/* keys themselves are catalogued in IN-13 and AD-13.
Domain Layer
Modern Domain / REST (src/)
None for this flow. The only modern adjacency is the REST checkout's MarketingConsentCaptured event (src/Domains/Order/Event/MarketingConsentCaptured.php), dispatched from PlaceOrderService.php:272 for future CRM-sync listeners — it does not call Mailchimp (no listener wired; in-code note PlaceOrderService.php:266-268).
Legacy Layer (ecommercen/)
| File | Responsibility |
|---|---|
ecommercen/eshop/controllers/Adv_mailchimp.php:1-105 | Storefront subscribe controller — mailchimpSubscribe() :26-51, getMailchimpConfig() :59-72, getJsonInput() :80-88, jsonResponse() :98-104. |
application/modules/eshop/controllers/Mailchimp.php:10 | Concrete HMVC controller (extends Adv_mailchimp) the routes resolve to. |
application/libraries/Mailchimp_Api.php:3 | Client-overridable thin wrapper (extends Adv_Mailchimp_Api). |
ecommercen/libraries/Adv_Mailchimp_Api.php:1-150 | Mailchimp Marketing API v3 client (mailchimp/marketing ^3.0). addMemberToList() :46-48. Canonical home: IN-13. |
ecommercen/eshop/controllers/Adv_order.php:352-357, 1809-1815, 1846-1891, 1893-1899 | Checkout-side trigger: displayMailchimpNewsletterCheckbox(), registerCustomerToMailchimp(), sendPostRequestWithCurl(), validateMailchimpRegistryValues(). |
Storefront views/JS: footer modal markup application/views/main/components/homepage/newsletter/newsletter_section_mailchimp.php (gated by MAILCHIMP/ENABLED); footer handler application/views/main/components/footer/footer_js.php:580-641; checkout checkbox assets/main/vue/CheckoutPage.vue:659-675 (option :753-758), Vue model assets/vue/mixins/checkoutPage.js:20.
Configuration
All config is registry-based (reggroup='MAILCHIMP'); there are no .env keys. Keys are written by the admin page (ecommercen/settings/controllers/Adv_settings.php:2332-2376) and read by this flow. The full key catalogue (ENABLED, API_KEY, SERVER_PREFIX, SUBSCRIBE_AS_PENDING, SITE_*, CHECKOUT_*, CUSTOM_*) is documented in IN-13; the keys this flow reads directly:
| Key | Read at | Effect |
|---|---|---|
MAILCHIMP/ENABLED | footer_js.php:580, newsletter_section_mailchimp.php:1, Adv_order.php:1811 | Gates the footer modal + checkout checkbox |
MAILCHIMP/SUBSCRIBE_AS_PENDING | Adv_mailchimp.php:65,70 | status='pending' (double opt-in) vs 'subscribed' |
MAILCHIMP/SITE_LIST / SITE_INTEREST / SITE_TAG_NAME | Adv_mailchimp.php:67,68,69 | Site-form audience/interest/tag |
MAILCHIMP/CHECKOUT_LIST / CHECKOUT_INTEREST / CHECKOUT_TAG_NAME | Adv_mailchimp.php:62,63,64 | Checkout audience/interest/tag |
config['csrf_protection'] = false globally (application/config/config.php:131) — applies to /api/mailchimp.
Client Extension Points
application/libraries/Mailchimp_Api.php (extends Adv_Mailchimp_Api) is the client-overridable wrapper — a fork can override API behavior there. Helper methods on Adv_mailchimp were widened private→protected (git 7ff5374ced) to support storefront-controller overrides.
Business Rules
- Email required, presence only — empty email → 400 (
Adv_mailchimp.php:30-32). Format is not validated server-side (the regex check is client-only,footer_js.php:584). - Form-type routing —
url === 'preview_order'selects the checkout list/interest/tag; any other URL selects the site set (Adv_mailchimp.php:34,getMailchimpConfig:59-72). - Double opt-in toggle —
SUBSCRIBE_AS_PENDING→ Mailchimpstatus='pending'(confirmation email) vs'subscribed'(immediate) (:39; git91a82344c7). - Interest tagging — if an interest id is configured,
interests => [$interestId => true]; else(object)[]so Mailchimp receives{}not[](:40). - Tag by name — the payload uses the tag name (
*_TAG_NAME), not the tag id (:41,64,69). - Checkout gating — checkout subscribes only if the
register_mailchimp_newsletterPOST flag is truthy and all four keys (ENABLED,API_KEY,SERVER_PREFIX,CHECKOUT_LIST) are set (Adv_order.php:352-357,1809-1815,1893-1899). - No local persistence / no state machine — subscription is fire-and-forward to Mailchimp.
Known Issues & Security Gaps
- API key stored in plaintext —
MAILCHIMP/API_KEYis written and read unencrypted (Adv_settings.php:2354,Adv_Mailchimp_Api.php:34); a full-access Mailchimp credential sits inregistry.regval(TEXT). Should use the encrypted registry path. - No CSRF / captcha / rate limiting on public
/api/mailchimp—csrf_protection=false; the endpoint (Adv_mailchimp.php:26) has no captcha, throttle, or auth. Any client can subscribe arbitrary emails — an abuse vector (especially withSUBSCRIBE_AS_PENDINGsending confirmation emails to arbitrary addresses). Contrast the REST auth endpoints'LoginThrottle. - Server-side email format not validated — only presence is checked (
:30); format validation is client-only (footer_js.php:584). A direct POST bypasses it. - Unguarded
$postData['url']—:34readsurlwithoutisset/??; a body omittingurlemits a PHP 8.1 "Undefined array key" warning (routes to 'site'). Theemailkey is guarded;urlis not. - Dead SSL-verification options in checkout cURL —
Adv_order.php:1879-1880setsCURLOPT_SSL_VERIFYHOST/VERIFYPEERaftercurl_exec()(:1877), so they never apply; the self-call's SSL hardening is a no-op. echomid-checkout on cURL error —Adv_order.php:1856,1885echo raw error strings (incl.curl_error) duringpreviewOrder, which can corrupt the checkout response and leak internal detail.- Synchronous blocking self-HTTP call during checkout —
registerCustomerToMailchimp(Adv_order.php:1853) makes a blocking cURL round-trip to the app's own/api/mailchimp(which then calls Mailchimp), serializing two network hops into checkout with no timeout and no deferred-task offload. CUSTOM_SITE_TAG/CUSTOM_CHECKOUT_TAGread but never written —Adv_settings.php:539,544read these keys, butsaveMailchimpConfigurationonly writes*_TAG_NAME(:2363,2370) — orphaned reads that always return null.- Custom-tag-name save bug —
Adv_settings.php:2363,2370persist$site_tag_name/$checkout_tag_name(the selected tag's name) intoCUSTOM_*_TAG_NAME, never the$custom_site_tag/$custom_checkout_tagadmin-entered values, which are silently discarded. - No footer-modal error feedback —
footer_js.php:627-639handles only the success branch andthrows on failure with no.catch(); a failed subscription shows the user nothing. - No dedup/idempotency — repeated submits re-call
addListMember; an already-subscribed member surfaces as a generic 500 with the raw exception indetails(Adv_mailchimp.php:48-49). - Dead view file —
application/views/main/components/modals/newsletter/newsletter_container.php:1-51is an entire legacy embed form commented out with/* */, never rendered.
Tests
None. No unit/integration/legacy test exercises Adv_mailchimp, Adv_Mailchimp_Api, registerCustomerToMailchimp, or the subscribe path (verified: find tests -iname '*mailchimp*' empty). The pending toggle, form-type routing, and the checkout cURL self-call are all untested.
Related Flows
- IN-13 Newsletter Integrations — canonical home for
Adv_Mailchimp_Api, theMAILCHIMP/*registry keys, double opt-in, and the two-list pattern. Mailchimp is one of several parallel providers (Manago/Moosend/Apifon). - AD-13 Settings — the admin
settings/mailchimpconfiguration page (RBAC, save path). - CF-06 Order Preview — the checkout flow that fires the server-to-server subscribe.
- SY-19 Config & Registry — the
registrytable and its caching behaviour.