Skip to content

Marketing consent

A customer’s marketing consent is kept per channel: email, sms (text message), phone and post. Each is granted, withdrawn, never_asked, other_address or unconfirmed. Every customer starts as never_asked; nothing is assumed.

Every answer is stored with the email address it was given for. A yes counts only while that is still the customer’s email: change the customer’s email, and the old yes shows as other_address and drops off lists and exports until the customer agrees again. For email the address must match exactly. For the other channels, a yes given while the customer had no email stands on its own.

unconfirmed is a tick from a guest checkout on the customer that order made: nobody proved the address, so it never counts and is left out of lists and exports. It is never turned into a yes for somebody else: when a person later makes a shop account with that address, only their own tick at sign-up counts. An unconfirmed tick lapses after 30 days and then shows as never_asked; the history keeps it.

Merging a duplicate into a customer brings only a no across: where the duplicate withdrew and the kept customer has not (a yes, an unconfirmed tick, or never asked), the kept customer is withdrawn too, written as office with the note “Merged from …”. A yes never moves; the duplicate’s history stays with it.

Erasing a customer withdraws every channel. If the customer row is kept, the history keeps what was agreed and when, but who and any note become Erased and the address is cleared. If the customer is deleted, the history goes with them. Erased customers are never on a marketing list.

The office, the shop and the API read and write the same record, so a change anywhere shows everywhere straight away.

GET /customers/{id}/marketing
Authorization: Bearer btl_…

Needs customers:read. The answer has each channel as it stands, with when it changed, the source and who, then the history newest first:

{
"channels": [
{
"channel": "email",
"state": "granted",
"changedAt": "2026-10-04T09:12:00.000Z",
"source": "shop_signup",
"by": "Sam Shopper"
},
{ "channel": "sms", "state": "never_asked", "changedAt": null, "source": null, "by": null }
],
"history": [
{
"id": "0192…",
"channel": "email",
"state": "granted",
"source": "shop_signup",
"method": null,
"by": "Sam Shopper",
"byKind": "customer",
"wording": "Send me news and offers from Northgate Gas by email. I can stop them at any time.",
"note": null,
"at": "2026-10-04T09:12:00.000Z"
}
]
}

Sources are shop_signup, shop_checkout, shop_account, office, import and api. wording is what the shopper saw beside the box, word for word.

GET /customers?marketing=email (and /customers/ids, and the export) lists only customers who granted that channel.

PUT /customers/{id}/marketing
Authorization: Bearer btl_…
Content-Type: application/json
{ "channel": "phone", "state": "granted", "method": "phone", "note": "Asked on the round" }

Needs customers:write, which owners and admins have and drivers do not. Recording agreement needs method: how the customer told you (phone, in_person, in_writing, email or other). Withdrawing needs only the channel and is always allowed, even while the business’s bill is unpaid.

A person signed in is recorded as source office; an API key or connected app as api, under its own name. Setting a channel to the state it already has changes nothing. An erased customer can be withdrawn but not granted (409). Bottle staff in a support session are refused (403), because the change would carry the name of the person they are looking as.

  • Sign-up has an unticked box, separate from the terms. A tick is held on the shopper’s account until they confirm their email, then given to the customers with that email as shop_signup, with the wording and the time they ticked it.
  • Checkout has the same unticked box. Left unticked it changes nothing. Ticked, it is given as shop_checkout only from a signed-in shopper with a confirmed email, and only to their own customer: the one they are buying as, when they may speak for it (a household, or a trade customer they manage; not a buyer or accounts person), or a customer the order made from the email they typed, when that is their confirmed address. A guest’s tick on a customer the order made is kept as unconfirmed and lapses after 30 days. A guest whose email matches an existing customer gives that customer nothing.
  • The account page has Marketing preferences: a switch for each channel, for each customer the account speaks for (a household, or a trade customer it manages). GET and PUT /storefront/sites/{host}/account/marketing need a signed-in shopper, and the PUT must come from the shop’s own page (its Origin and a JSON body). Another customer is 403.

Consents recorded on the shop before this record existed (a confirmed shop account’s yes, and ticks at signed-in checkouts) were carried over as granted for email, with their original date, words and source. Guest checkout ticks were not, as nobody proved the address.

Every consent change is recorded in the history. Existing entries cannot be edited through the API. Privacy erasure removes identifying information or deletes the history along with the customer, as described above. The history is included in the customer’s data download.

The MCP tool get_marketing_consent reads a customer’s consent and history. No tool changes it.