Mailchimp
Paid add-on. Every consented submission subscribes or updates a member of a Mailchimp audience.
This document describes what the module guarantees and what it refuses. The user guide gives the short version, under “Subscribing to a Mailchimp audience”.
Without consent, nothing goes out — not even the contact
That is the difference from HubSpot and ActiveCampaign, and it is not an arbitrary hardening.
Those two are CRMs: the record there is an act of administration, distinct from the mailing list. The contact therefore always goes out — somebody wrote to the site — and only the subscription waits for agreement.
Mailchimp is the mailing list. Putting somebody on it is subscribing them: there is no administrative record to file first. A module that sent “only the member” without consent would be a silent export of contacts to a sending platform.
Three things are therefore required on the same footing: an audience, an address field, and a consent field. Until all three are set, no submission goes out, and the panel says so.
With no box ticked by the person, nothing is even scheduled: there is no queued row, no task, no trace. There is nothing to send.
Consent reads in two ways, and that is where we got it wrong
The core’s Consent field records {accepted, text, at}: the exact wording
accepted, and the date. Only that field will do — an ordinary checkbox records
only a “yes”, and a “yes” without the wording it approved demonstrates nothing.
For a mailing-list subscription, that is precisely what you will be asked to
produce.
That value has two shapes. In memory, just after the submission, it is an array. Read back from the database, it is a JSON string: the repository serialises composite values.
Three modules read accepted after checking that the value was an array, and
otherwise fell back on “a non-empty value means yes”. On the read-back shape,
{"accepted":false,…} is a non-empty string — a refusal read as an agreement.
Two of those modules only read consent at submission time and were not affected;
this one re-reads it at send time, and was.
The reading is now shared by all three, in a single place that knows both shapes. On this question, unknown means no.
pending by default: double opt-in
pending has Mailchimp write to the person, who confirms by clicking. That is the
default value and the one the screen recommends.
subscribed skips that confirmation. It is not forbidden — the module records
precisely the proof that would allow it — but it is for the data controller to
answer for, and the screen writes it out before letting you choose: without
confirmation, nobody checks the address, a typo subscribes a stranger, and the
consent you hold names somebody else.
The connection screen shows the number of members awaiting confirmation. That is the figure you come looking for when you doubt double opt-in: a number that never goes down says the confirmation emails are not arriving — an outage you would not otherwise notice, because everything looks fine from this side.
status_if_new, not status
That is the detail that stops the module doing harm.
status would impose the status on every send: somebody who had unsubscribed
would find themselves resubscribed on the next submission of a form they once
filled in — or by a simple click on “Resend” in the admin.
status_if_new only applies at creation. An existing member keeps the status they
chose, and the module confines itself to updating their fields.
Mailchimp refuses to resubscribe whoever unsubscribed
Somebody unsubscribed, bounced or having reported abuse is answered 400 with a
message saying so. Only they can come back, from a Mailchimp form.
That refusal is definitive: replaying it three times would not get round it, it would insist. The submission’s screen distinguishes it from a settings error and explains it — and offers no resend button there, because there is nothing to resend.
The address fingerprint is never kept
Mailchimp designates a member by the MD5 of their lowercased address. That is not the module’s choice, it is the API — and it is not an innocuous identifier: given an address, the fingerprint is verified in a second. It is an address in disguise.
It is therefore computed at the moment of the call and never written: not in
the tracking table, nor in the delivery log, where the address called is reduced
to lists/members — without the fingerprint, and without the audience’s id.
Erasing a submission takes its tracking row and its log with it. But a kept fingerprint would have survived in backups and exports, and would have read there as an innocuous technical datum when it designates a person.
What is kept instead is the web_id, the numeric identifier Mailchimp returns
with the member: it opens their record and says nothing about anybody.
Merge fields belong to the audience
Not to the account. FNAME exists almost everywhere, MMERGE7 nowhere else.
Offering them from the whole account would lead to mapping a tag absent from the audience targeted — and an unknown tag has the whole subscription refused, not just the field. The screen therefore reads them for the chosen audience, and the setting bounds the tags to Mailchimp’s format: uppercase, ten characters at most.
EMAIL cannot be mapped: the address has its own setting, and mapping it a second
time would send two of them, one of which does not serve to designate the member.
An empty value is not sent: a merge field sent empty erases the member’s, and a second submission with no first name would delete the one the first had put there.
The token does not expire, and there is nothing to refresh
Mailchimp returns expires_in: 0 and no refresh token. That runs counter to the
other integrations’ habit: there is no cycle to keep up, and in exchange a secret
that is worth as much as long as nobody revokes it.
It is therefore encrypted at rest, with a key of this integration’s own. And disconnecting revokes it explicitly at Mailchimp’s — erasing it here would not be enough. If that revocation fails, the screen asks you to go and remove the application on the Mailchimp side, because otherwise the access stays alive at a third party’s, with no end date.
The API’s address arrives over the network
login.mailchimp.com carries the authorisation; the API lives on the account’s
data centre — us19.api.mailchimp.com — obtained by a second call after the
exchange, on an endpoint provided for that.
That address therefore comes from a network response, and it is the one we will
call with a token. It is checked against the .api.mailchimp.com suffix and the
https scheme — the leading dot being what separates a Mailchimp data centre from
an attacker-api.mailchimp.com.
An exchange that returned a token with no acceptable root is treated as a failure: better to refuse the connection than to record one that points nowhere.
What is not attempted, and what is retried
Nothing is scheduled without consent, without an audience, without an address field, without a connection, or if the form’s condition is not met. A submission marked as spam does not go out. A malformed address is a definitive refusal written before any call.
Definitive refusals are not replayed: audience gone, unknown tag, resubscription
refused. 429 and 5xx are, at 1, 5 then 30 minutes.
A failed tagging does not take back an acquired subscription. The person is subscribed, and the reason is written beside it.
A Mailchimp outage never blocks the submission. It is recorded, confirmed to the visitor and notified by email before this module is called on.
Resending resubscribes nobody
The resend button replays the PUT with status_if_new: an existing member keeps
the status they chose. A resend therefore updates their fields, and can neither
resubscribe them after an unsubscribe nor pull them out of awaiting
confirmation.
It does not appear on a submission without consent, since there is no row.
Schema
slf_mailchimp_members: one row per subscribed submission — and none for the
others — with the audience, the web_id, the status Mailchimp returned, the
tagging, the state, the reason for the last problem and the number of attempts.
There is no consent column: it could only have had one possible value, and carrying it would have suggested there exists a state in which it is zero.
The connection lives in an option: the client id and the API root in clear — they are not secrets, and the root is the first thing to check when nothing is going out — the token and the secret encrypted.
What V1 does not do
No campaign, no segment, no commerce, no reading of the audience, no member deletion, no reverse synchronisation.
And no handling of unsubscribes: unsubscribing belongs to Mailchimp and to the person. A form must not be able to undo it — nor, for that matter, to perform it on their behalf.
