Skip to content

Customer Messaging and Consent

Badger keeps a record of who has asked to hear from your shop, what they agreed to, and when. It covers the sign-up tab, the marketing box at checkout and on the registration form, the choice customers make in their account, the terms of sale shoppers accept when they order, and enquiries sent through your contact form. You see each customer's consent on their page in the admin and download your subscribers as a list for your mailing tool, or send them email campaigns from Badger.

What is recorded

Record What it holds Where it comes from
Contact One per person per shop: their name, language and the email address (or addresses) you can reach them on. Separate from a customer account, so someone can subscribe without ever registering. The sign-up tab, checkout, the contact form
Consent Every decision, kept for good: agreed to or declined news and offers, the list, the date, where it was given, how (a ticked box, a confirmation link, a one-click unsubscribe) and the exact wording the person saw. Decisions are only ever added, never edited. The sign-up tab, checkout, unsubscribe and confirmation links
Terms accepted The terms of sale a shopper accepted, with the order: your version label and the exact text checkout showed them (see Terms of sale: what is kept). Checkout
Contact requests Enquiries sent through a contact-us form: name, email, phone, subject and message. The contact form

Consent is given to your shop, so a contact belongs to one shop and is never shared between shops. A shopper who signs up as a guest and registers later with the same email address keeps their choices: they are found by email, with nothing to merge.

A customer's contact and consent history appears in their subject access request (Customers, open the customer, then the subject access request page): the contact details, the addresses, every consent decision with its date and source, and any enquiries. These tables are empty for a customer who never left an email address, signed up, or wrote to you.

Settings

Under Settings, Customers and marketing and Settings, Store, Terms of sale and privacy notice:

Setting What it does Default
Marketing opt-in at checkout (checkout-marketing-opt-in) Unless it is off, the registration form also offers an unticked Email me news and offers box (see Registering). off: checkout doesn't ask. opt-in: an unticked Email me news and offers box. soft-opt-in: a Don't email me box, for the UK rule on similar products to existing customers only. Someone who unsubscribed before is never signed up again this way: a signed-in shopper sees the plain Email me box instead, and a guest who didn't tick anything stays unsubscribed. A later sign-up they haven't confirmed yet (a grant still waiting for its confirmation click) doesn't change that. soft-opt-in. Existing shops get it written explicitly when they apply the Customer messaging and consent upgrade (below)
Confirm newsletter sign-ups by email (marketing-double-opt-in) People who sign up on a form (the sign-up tab, the registration form, the booking form, the account Preferences tab unless the account proved the address with its own order) must click a link in an email before they are subscribed. Bookings your staff take always ask the guest to confirm, whatever this says. While it is on, the subscriber export includes only proven addresses and checkout subscriptions. On
Terms of sale page (terms-of-sale-page) The page holding your terms. Checkout links to it. With none, checkout says nothing about terms. None
Terms of sale version (terms-of-sale-version) Any label you choose, such as 3 or 2026-10-23. It names the version on records and in the subject access request; change it when you change the terms. 1
How customers accept the terms (terms-acceptance-mode) statement (By placing this order you agree...) or checkbox (a box to tick before paying). statement
Privacy notice page (privacy-notice-page) The page holding your privacy notice. Checkout, the sign-up tab and the account page link to it, and each text is kept as for the terms. With none, they link to the privacy notice URL (privacy-policy-url). None
Privacy notice version (privacy-notice-version) A label for the current privacy notice; change it when you change the notice. 1
Keep contact requests for (months) (contact-request-retention-months) How long enquiries are kept before they are deleted. 24
Reply-to address for replies to enquiries (contact-request-reply-to) Where a customer's answer to your reply goes. None: your order notification address
Keep inactive contacts for (months) (contact-retention-months) Contacts who never made a marketing choice and haven't ordered or been in touch for this long are erased. 0 keeps them. 24

The words shoppers see are Site Text, so you can change or translate them: consent.marketing.checkout, consent.marketing.checkout.softOptOut, consent.marketing.privacy, consent.terms.statement, consent.terms.checkbox, emailCapture.consentNote, login.registration.marketing, preferences.topic.label, messaging.unsubscribe.question and the others beginning messaging. and preferences.. Whatever was on screen when someone agreed is what is recorded.

Sign-up tab and double opt-in

The Email Capture tab records a real subscription. With Confirm newsletter sign-ups by email on, the visitor is told to check their inbox, and is subscribed only when they click the link in the confirmation email and then presses Confirm on the page it opens (the marketingConfirm email, see Transactional Email). The email is sent at most once per address per 24 hours and the link works for 7 days. One click confirms every list the address signed up to and hasn't confirmed yet, so someone who joins a second list the same day needs only the one email. Once they have confirmed, the next list they join sends a new email straight away. This proves the address belongs to the person and stops anyone signing up someone else. The button matters: mail scanners open every link in an email, and opening the page alone confirms nothing. A link that can no longer do anything (expired, the contact erased, or the sign-up withdrawn since) shows "this link no longer works" instead of the button. The sign-up tab also gives the same "check your inbox" answer for an address that is already subscribed, so it can't be used to find out who is on the list. A sign-up that is not confirmed stays pending and is never emailed marketing.

Checkout opt-ins are not confirmed by email: the shopper has just ordered using that address. Every other form that can subscribe someone (the booking form, the account Preferences tab when the account hasn't proved the address itself) follows the same rule as the sign-up tab. A list ticked on the Preferences tab that is still waiting is confirmed as soon as the account places an order with its own email, since that proves the address too (the record says so). Sign-ups from the sign-up tab or a booking still need the click.

Someone who opted out needs the click, whatever the setting. When the person unsubscribed from the list themselves (an unsubscribe link, their mail app's unsubscribe button, a spam report, or unticking it or Stop all marketing emails on their account), or their address is on the do-not-email list because they unsubscribed and were later erased, reported spam, or bounced, every way of subscribing them (a ticked box at checkout, the sign-up tab or registration with Confirm newsletter sign-ups by email off, the Preferences tab, a booking, an import, or an order the account places with its own email) waits for the confirmation link instead, and the confirmation email goes. Until they click it they get no campaigns, and their record shows the list as waiting for confirmation. Checkout is never held up by this. An unsubscribe made by staff, or an erasure, doesn't count as their own.

Registration and the account page

Registering

The registration form links your privacy notice (the Privacy notice page, else Privacy notice URL) when you have one, and, unless Marketing opt-in at checkout is off, shows an unticked Email me news and offers box. A ticked box is a sign-up like the sign-up tab: with Confirm newsletter sign-ups by email on, the new customer gets the confirmation email and is subscribed once they confirm; with it off they are subscribed straight away. It is recorded as coming from registration, and the privacy notice is noted against them once they are subscribed (at once, or when they click the confirmation link). An unticked box records nothing.

The account overview

A signed-in customer's overview has an Emails and terms card: the lists they get, or that a sign-up is waiting for its confirmation link, and the terms of sale version they last accepted, with a link to that text. The card only shows what the Preferences tab shows, with the same rules about what counts as the customer's own history, and links to it: that tab is the one place a customer changes their emails. When your terms have changed since they accepted them, the overview also shows the terms changed notice.

Unsubscribing

Every marketing email carries an unsubscribe link, which opens /messaging/unsubscribe/...:

  • It works without signing in and never shows the email address.
  • Opening the link changes nothing. The shopper presses the button, so mail scanners that open links cannot unsubscribe anyone.
  • The shopper can stop one list or every email from the shop. An unsubscribe always wins: a sign-up or confirmation made just before it, landing a moment later, doesn't undo it.
  • Mail apps that offer their own Unsubscribe button use the same link as a one-click request, and it is recorded as such.
  • The link never expires and only works for the shop that sent it. Links are signed with a key each shop creates for itself the first time it needs one (messaging-link-signing-key); it is not shown in Settings (removing it would break every link already sent) and is not copied by site transfer.

In the admin

A customer's page has a Marketing card: the lists that customer is on and how many messages they sent through your contact form, with a link to their contact, which shows every decision they made (see Contacts in the admin). The subject access request pack lists the same decisions.

Customers > Download email list opens the Export subscribers panel on Contacts (see Exporting subscribers).

Contact requests

Enquiries from a contact-us form are stored before the email to you is sent, so a message is never lost if the email fails (the request is marked when the notification fails). The notification email quotes a reference. Sending an enquiry does not subscribe the sender to anything. Requests are deleted automatically after Keep contact requests for (months).

On the single-page checkout, the page shows what the shopper is agreeing to, and the order records it. The detail of how the page looks is in Basket and Checkout.

  • What the shopper sees. In statement mode the terms sentence sits under the Pay button and under the Apple Pay / Google Pay button. In checkbox mode a box sits above both, and ticking one ticks the other; until it is ticked the Pay button reads Accept the terms of sale and the wallet button is dimmed. The marketing box sits by the email field, and is not shown to a signed-in customer who already gets news and offers.
  • What is kept on the order. As the shopper makes choices they are saved with their basket: whether they opted in to marketing, whether they accepted the terms, and a note of exactly what the page showed (the terms version, a fingerprint of the terms text, the marketing mode, the wording and the language). The note is refreshed each time the checkout changes and when the shopper pays, so it holds the terms as they read at that moment.
  • When it is recorded. When the order is placed, the order pipeline's recordCheckoutConsent step turns those choices into consent records against the shopper's contact. This happens the same way whether the shopper came back from paying or the order was completed later by Stripe's payment notification. Not ticking the marketing box records nothing. A failure to record never stops an order; it is logged with the order's reference.
  • Checkbox mode is enforced. A payment that arrives without the box ticked (a checkout page left open from before) is refused with Please accept the terms of sale to place your order, and the hold on the card is released.
  • Shops with their own order pipeline. A shop that has customised its submitOrder pipeline lists its own steps, so it needs the recordCheckoutConsent step in it. The upgrade below adds it.

Terms of sale: what is kept

Your terms page is an ordinary page that you can edit at any time, so Badger keeps the words themselves. When checkout shows the terms, it reads the page's text and takes a fingerprint of it (a SHA-256 hash). The first time a text is seen, a copy of it is kept with its fingerprint, your version label and the date. Each acceptance records the label and the fingerprint, so it always leads to the exact text that shopper was shown, even if the page has changed since.

  • Every distinct text is kept once. Many orders accepting the same text share one copy. Changing the label without changing the words keeps the copy you already have.
  • Edit the page, change the label. If you edit the terms but keep the same label, each acceptance still points at the right text, but two different texts now share one name, which is confusing in a dispute or a subject access request. Badger logs a warning when that happens. Change the label whenever you change the terms.
  • What the shopper sees in a subject access request is the label, with a note that the exact text they were shown is kept and can be sent on request.
  • Orders completed by Stripe's payment notification use the fingerprint noted while the shopper was on the checkout page; nothing is read from the page afterwards.
  • If the text can't be read (a fault, logged with the order's reference), the acceptance is still recorded with the label alone.

Turning it on for an existing shop

Apply Customer messaging and consent under the site upgrades. It:

  1. sets checkout marketing opt-in to soft-opt-in (a shop that already chose opt-in keeps it);
  2. sets Terms of sale page if you have none and a page named terms, terms-and-conditions, terms-of-sale or terms-conditions exists;
  3. adds the consent step to your own order pipeline, if your shop has one without it.

It then asks you to check the terms page and version. Applying it twice changes nothing. Earlier consent is not carried over automatically. If you want it, apply the separate upgrade below.

Then apply Versioned privacy notice. It sets Privacy notice page if you have none: to the page your Privacy notice URL already points at, when that is one of your own pages (/p/...), else to a page named privacy, privacy-policy, privacy-notice or privacy-and-cookies. It sets Privacy notice version to today's date if you haven't set one, and asks you to check both. With no such page it changes nothing and tells you to choose one (see Legal documents and the privacy notice).

Apply Import earlier marketing consent under the site upgrades when you want restaurant guests who ticked the marketing box on your booking form to be able to receive your marketing emails. It is a one-time import you start yourself, once per shop; it is never applied for you.

What it imports. Every dining guest with a marketing consent time and an email address:

  • becomes a contact (or joins the contact with that email) and is linked to their guest profile;
  • gets a marketing email consent on the News and offers list, dated when they really ticked the box, so the consent history shows the true date. It is labelled Imported and Copied from the shop's earlier records, and holds the booking form's wording (Send me news and offers by email...) as the evidence;
  • is not sent anything. The import sends no email.

Unsubscribes win. Badger never lets an older record override a newer decision. A guest who unsubscribed after they ticked the box stays unsubscribed; their grant is logged for the record and counted as already unsubscribed.

Running more than once. The import runs once per shop. Applying it again does nothing, and if it stopped part-way (the worker restarted, say) it carries on from where it got to. A guest is never imported twice.

Large guest lists. The work is queued for the background worker and handled in pages of 200 guests, so a shop with tens of thousands of guests doesn't tie up the admin. The upgrade shows as applied as soon as it is queued.

Counts. The worker records, per shop: guests looked at, imported, left out because they gave no email, already imported, already unsubscribed (including a guest whose imported tick is waiting for a confirmation click because they unsubscribed before it), and any it couldn't handle. They are stored on the shop's import job (legacyConsentImportJob) and written to the log as numbers with the site id only, never names or addresses. The upgrade's card shows where the import is and its counts, for example Finished on 23 Oct 2026. Imported 1,203, already unsubscribed 4, no email 162, couldn't handle 2., or how many guests it has looked at while it runs.

What is not imported: the old newsletter pop-up. Before customer messaging, the newsletter dock only copied the visitor's name and email onto their (usually anonymous) user, and recorded nothing about consent. Those users can't be picked out reliably: checkout, the contact form and the dock all fire the same event (UserInformationAddedEvent) and fill in the same fields, and nothing on the user says which form it came from. Importing every user with an email would also pull in people who only placed an order or sent a message. So these sign-ups are left out entirely. To re-permission them, invite them to sign up again through the new form.

Communication preferences

Signed-in customers manage their emails on the Preferences tab of their account (/p/account/preferences, see Customer Account):

  • A tick box per list. News and offers first, then each of your mailing lists that is set to show on the preferences page and isn't archived. Under a list they get, the page shows the date and the exact statement they agreed to, as recorded. The page says which address the emails go to.
  • Ticking a list subscribes them straight away when the account itself has proved the address (the customer placed an order with it while signed in to this account) and they haven't unsubscribed from that list before. A list they unticked or stopped earlier waits for the confirmation email. Otherwise, with Confirm newsletter sign-ups by email on, the list waits as Waiting for you to confirm your email address and the confirmation email goes to the address (at most once a day, as for the sign-up tab), even when that address was confirmed before by someone else's click. The decision is recorded as made on the account page with a ticked box, with the statement Email me about {list}. A customer who has never signed up gets a contact made from their account email the first time they tick a list.
  • Unticking a list unsubscribes them from it at once; Stop all marketing emails unsubscribes them from every list, whatever is ticked. Emails about their orders and bookings still come. With no contact yet there is nothing to stop, and nothing is written.
  • Terms of sale. You accepted our terms of sale (version 3) on 14 Oct 2026, with a link to the exact text they accepted, and a link to the current terms when they have changed since.
  • The privacy notice is linked under the form, and noted on the customer's record when a list they tick is subscribed (at once, or when they click the confirmation link).

What the page shows of decisions made elsewhere. An account is matched to the shop's contact by its email address, and creating an account doesn't check that the address belongs to the person creating it. So the page treats only what this account did itself as the customer's own history:

  • The date and the statement agreed to are shown for a list only when this account made that decision (ticked it here, or at checkout while signed in). A subscription made another way under the same email (a guest checkout, the sign-up tab, a booking) shows as ticked, with no date or wording.
  • The terms line and the terms changed notice appear only for terms accepted at checkout while signed in to this account.
  • The customer can untick anything shown ticked (stopping emails is always safe), but ticking a list asks the address to confirm unless the account proved the address itself, as above.
  • Enquiries sent through the contact form are never shown on the account.

The tab shows only to a customer signed in with their password (or central sign-in). Someone recognised only by a remembered cookie sees a sign-in prompt instead, and the form only ever changes the signed-in customer's own choices.

Terms changed notice. When you publish new terms of sale, customers who accepted an earlier text see one line on their account overview: We've updated our terms of sale. The new version applies to your next order, with a link to the new terms. It starts when checkout first shows the new text and stays for 90 days; there is nothing to dismiss. It compares the text itself, so a typo fix under a new version label shows nothing, and an edit without a new label still counts (shops whose terms text couldn't be read compare the labels instead).

The stored text page. Every terms or privacy notice text that checkout has shown has its own page, /legal/terms-of-sale/{code} or /legal/privacy-notice/{code}, showing the title, your version label, the date it was first shown and the text exactly as it was, with a link to the current version. It holds no customer details, isn't indexed by search engines, and only finds your own shop's texts.

Contacts in the admin

Customers > Contacts lists everyone you can email: shoppers who ticked a box at checkout, people who signed up in the footer or on a page, people who wrote to you through a contact form, and diners who booked a table. A contact is one person, however many times they have ordered or signed up. This page is for admins only.

Finding someone. Search by email address (type the start of it) or by part of a first or last name. A single word finds both: ada lists people called Ada and people whose address starts with ada, each once. Sort by newest, latest activity or last name.

Lists and status. The three counts at the top are for one list at a time: subscribed, waiting to confirm (they signed up but haven't clicked the link in the confirmation email yet), and unsubscribed. Pick a list with the List menu, then use the status buttons to see only the people in that state. Erased contacts are hidden unless you choose Show erased contacts.

A contact's page shows:

  • their name, the language they are emailed in, where they first came from, and links to their customer account or dining guest profile when they have one;
  • their email addresses and whether each one works (an address that bounced or was reported as spam shows a warning);
  • each list and whether they are subscribed, with a button to unsubscribe them;
  • What they agreed to: every decision, newest first, with the date, how it was made (checkbox, link in an email, you on their behalf) and the exact wording they were shown at the time. Decisions staff made for them name the staff member;
  • any messages they sent through your contact form.

Unsubscribing for someone. If a customer asks you by phone or in a shop to stop their emails, open their contact and choose Unsubscribe for one list, or Unsubscribe from all for every list. The record shows that you did it on their behalf. Staff can only unsubscribe people, never subscribe them: someone is subscribed only when they agree themselves.

Erasing a contact. Erase this contact (in the menu at the top of their page) removes their name and email addresses, stops all marketing to them and deletes the messages they sent through your contact form. You are asked to type the word erase to confirm, and it can't be undone. The record of what they agreed to and when stays, with no name or address on it, so you can still show you honoured their choices. Use it for a "please delete my data" request about marketing; a customer's orders are handled separately under subject access and deletion requests.

On a customer's account page. A Marketing card shows the lists that customer is on, with a link to their contact. When you mark a customer for deletion, the box Also erase their marketing contact and enquiries is ticked by default, so their email address isn't left behind in your contacts. Untick it to keep the contact (for example to keep honouring an unsubscribe); the contact is then simply no longer linked to the deleted account.

Exporting subscribers

On Customers > Contacts, the Export subscribers panel produces a spreadsheet (CSV) of everyone subscribed to one list, ready for an email tool or a mail merge.

  1. Choose the list. One list goes in each file; export another list for another file.
  2. Choose Export subscribers. The file is prepared in the background, which can take a minute for a long list.
  3. When its status under Recent exports says Ready, choose Download.

The file has one row per person with these columns: email, first_name, last_name, list, subscribed_at, source, method, locale and unsubscribe_url. locale is a language tag such as en-GB. unsubscribe_url is that person's own unsubscribe link for the list: if you send from another mailing tool rather than Email Campaigns, put it (usually as a merge field) in those emails, alongside the tool's own unsubscribe. It is the same signed link as in Badger's emails (see Unsubscribing): it works without signing in, asks before it changes anything, accepts a mail app's one-click unsubscribe, and the person drops out of the next export. It includes only confirmed subscribers: people who are subscribed and confirmed, whose email address works, and who haven't been erased. People who are waiting to confirm, unsubscribed or whose address bounced are left out, so the file is safe to send to.

While Confirm newsletter sign-ups by email is on, a confirmed subscriber must also have an address that is known to be theirs: one confirmed by clicking a link, or used for an order by the account with that email, or a subscription made at checkout (which, as above, is never confirmed by email: the address took the order). Anyone else, such as a subscription brought in by the import of earlier consent or one made while the setting was off, shows as subscribed on their contact but isn't exported until their address is confirmed. Turning the setting on therefore also narrows the export to proven addresses.

Addresses on the shop's do-not-email list are left out too. The export uses exactly the rule email campaigns are sent by, so the file and a campaign to the same list reach the same people.

Because it holds personal details:

  • only admins can ask for an export, and each download is recorded;
  • the file is deleted after 7 days and shows as Expired; ask for a new one if you need it later;
  • keep the file somewhere private and delete your copies when you are done with them.

Contact requests inbox

Customers > Contact requests is where the messages sent through your contact form land. It opens on the requests that are New; the tabs beside it show In progress, Closed, Spam and All, and the search box finds a name, an email address or a subject. The tiles above the list show how many are new and in progress and how long the oldest new one has been waiting, so you can see at a glance what needs an answer first.

Open a request to see what the person wrote, who they are (with links to their contact, their account and the order they were asking about, when there is one) and any replies already sent.

  • Reply by email. Write your answer in the box and press Send reply. It goes to the address on the request, from your shop's order notification sender, with the subject "Re: " followed by theirs, and their original message quoted underneath. Replies are plain text, up to 10,000 characters; line breaks are kept. The request moves to In progress, or to Closed if you tick Close the request after sending.
  • Where their answer goes. By default a customer who replies to your email writes to the order notification address. To send answers somewhere else, such as a support inbox, set Reply-to address for replies to enquiries in Settings > Customers. Leave it empty to add no Reply-To.
  • If the email can't be sent. Your reply is still saved on the request, marked Not sent, and you are told at once. The customer hasn't received it, so send another reply.
  • Status. Mark in progress, Close and Mark as spam change the status without sending anything. A request marked as spam can't be replied to; use Reopen as new if it was marked by mistake. A request with no email address (the form didn't ask for one) can't be replied to either.

Replies are kept with the request and deleted with it, after Keep contact requests for (months).

Mailing lists

Customers > Mailing lists holds the lists shoppers can choose to hear from you about, such as New in menswear or Events. Every shop has one built-in list, News and offers, which is always first. Its name and description are Site Text, so you reword them under Site Text in the shop settings; it can't be changed or archived here.

For each list you see how many people are subscribed and how many are waiting to confirm their email address.

  • Create a list. Choose a name (up to 80 characters), a short description (up to 300) and a key: a short name of lower-case letters, numbers and hyphens, such as menswear. The key is how the list is recorded against people's choices, so it can't be changed once the list exists.
  • Edit. You can change the name, description, order and whether the list is shown on the account preferences page. Turn that off for a list only your sign-up forms add people to. Lower order numbers come first.
  • Archive and restore. An archived list takes no new sign-ups and disappears from the preferences page, but everything people chose is kept, so nothing is lost and Restore brings it back as it was. Before you archive, you are told how many people are subscribed or waiting on it.

Your privacy notice is versioned the same way as your terms of sale: it stays an ordinary page you edit as you like, and Badger keeps each text it has shown, with a fingerprint, your version label and the date it was first shown.

Turning it on. Set Privacy notice page and Privacy notice version under Settings, Store, Terms of sale and privacy notice, or apply the Versioned privacy notice upgrade (see Turning it on for an existing shop). Until a page is set, checkout, the sign-up tab and the cookie banner link to the Privacy notice URL setting, if you have one, and nothing records which notice was shown: a plain web address has no versions.

Where the notice is recorded. Once the page is set, these links go to it, and each one notes which notice the person was shown:

Where When it is noted Against whom
Checkout The privacy link sits beside the marketing box. When the order is placed, the order pipeline's recordCheckoutConsent step notes the notice shown, from the note checkout kept on the order (so orders completed by Stripe's payment notification are covered too). The shopper's contact, with the order
Sign-up tab When the address agrees: at once when sign-ups aren't confirmed by email, else when the confirmation link is clicked (the notice recorded is the one shown beside the form at sign-up, kept on the pending sign-up until then). A sign-up for an address that is already subscribed, or one never confirmed, notes nothing, so typing someone's address writes nothing against them. The contact the sign-up found or made
Account Preferences tab As the sign-up tab, when a list ticked there is subscribed. The customer's contact
Cookie banner When a signed-in customer who already has a contact answers the banner, and the banner links to the privacy notice (no Cookie Policy Page is set). Anonymous visitors' choices are never linked to a person. That customer's contact

One record per person per text. A customer is noted once for each distinct text of the notice they were shown, not once per order or per sign-up. A regular customer gets a new record only when you change the notice's words. Each record carries your version label, the fingerprint of the text and the sentence shown beside the link (Read our privacy notice.), in the shopper's language.

It never gets in the way. Noting the notice never stops an order, a sign-up or a page from showing. If the notice's text can't be read at that moment, the record carries the label alone; if the note can't be written, the failure is logged with the order or site reference and the order goes ahead. The page's text is read once per edit and kept, so the notice adds no work to each checkout.

Customers, Legal documents (/admin/customers/legal) shows both documents:

  • The page and version now, and whether the text on the page has been shown to a customer yet. A text is kept the first time checkout (or the sign-up tab) shows it, never just because you opened this page, so a new text reads Not shown yet until someone sees it.
  • Every text kept, newest first: its version label, when it was first shown, its fingerprint and length, and how many records point at it. For the terms that is the number of acceptances (each order records its own); for the privacy notice it is the number of customers shown that text. The text on the page now is marked On the page now.
  • View text opens a kept text exactly as customers saw it, as plain text.

It warns you, and says what to do, when:

Warning What it means What to do
The text has changed but the version label hasn't The page's words differ from the newest text kept under the current label. Each record still points at the exact text the customer saw, but two texts share one name. Change the version label in the settings whenever you change the page.
More than one text has the same version label Earlier edits were made without a new label. Give the next version its own label.
The page can't be found The page named in the settings doesn't exist or is switched off, so nothing is shown or recorded. Choose another page, or switch the page back on.
The page's text couldn't be read Records carry only the label until it can. Check the page has text on it, then reload.
No page is set For the terms, checkout shows no terms; for the privacy notice, nothing records which notice was shown. Choose the page in the settings.

The page is for admins (ADMIN and SUPERADMIN) and only ever shows your own shop's texts.

Bookings

The table booking form has a box, Send me news and offers by email, that is never ticked for the guest. When a guest ticks it and gives an email address, the booking records a real subscription to your general list:

  • What is recorded. The guest becomes a contact (found by email, so a shopper who also orders or signs up has one contact, not two), the booking's guest profile is linked to it, and a consent record is written with source Booking, method Ticked a box and the exact wording the guest saw, in the language they saw it.
  • Confirmed by email, like the sign-up tab. Anyone can type any address into a booking form, so with Confirm newsletter sign-ups by email on the subscription waits for the guest to click the link in the confirmation email (sent at most once a day per address), even if that address was confirmed before. With the setting off it is confirmed straight away. Someone already subscribed gets no second record; someone who unsubscribed earlier and ticks the box again is asked to confirm again.
  • Bookings your staff take. Staff can't subscribe anyone. When a booking taken by phone, in person or through the staff booking tools has the box ticked (the guest said yes), the guest is sent the confirmation email, whatever the setting, and is subscribed only when they click it. The waiting record is marked Entered by staff and names the member of staff who asked; the confirmation is the guest's own.
  • Not ticked, nothing recorded. A booking without the tick, or without an email address, creates no consent and no contact. The tick on its own never adds anyone to a list they did not ask for.
  • A failure never stops a booking. If the consent can't be written the booking is still made, and the problem is logged with the booking and shop ids.
  • Earlier bookings. Bookings made before this existed are brought in once by the import of earlier consent; after that, new ones are recorded as they happen.

The restaurant guest profile keeps its own marketing consent date for the diary; the consent record is what the contacts list and any future sending use.

Keeping and deleting contacts

Contacts hold names and email addresses, so Badger keeps them no longer than there is a reason to.

When a customer account is deleted. Deleting an account (including the nightly clean-up of accounts marked for deletion) unlinks the account from the shop's contact: the contact keeps its subscriptions and consent history but no longer points at an account that is gone. Deleting a customer from the admin also offers Also erase their marketing contact and enquiries (ticked by default), which erases the contact as below. The nightly clean-up only ever unlinks; it never erases a contact on its own.

When duplicate accounts are merged. The contact linked to the account being merged away moves to the account that is kept, if the kept account has no contact on that shop; otherwise it is unlinked and the kept account keeps its own. Only the merging shop's contact changes. Proof of the address made by the old account (its own order) doesn't move with it: the kept account proves the address itself.

When a restaurant guest is forgotten. Forgetting a guest profile clears the guest from the contact it was linked to. The contact itself stays, with its consent.

Erasing a contact. Erasing removes the name and the email addresses and marks the contact erased. Any current subscriptions and sign-ups waiting to be confirmed are withdrawn, on every channel (the record names the member of staff; one made by the nightly clean-up below has the source Retention and no staff member), and the contact's enquiries and record of campaign emails sent to them are deleted. The consent records stay: they hold only ids, so they prove what was agreed and when without keeping the person. Erasing twice does nothing the second time.

Erased, but remembered. Before the address goes, erasing puts it on the do-not-email list when there is something to remember: the person had unsubscribed (their last choice was to say no, and they weren't still subscribed to any list), reported an email as spam, or their address bounced for good. The list holds a fingerprint of the address, never the address. So if the same address later places an order, the UK soft opt-in can't sign them up again, exactly as if they had never been erased. Ticking the box, a sign-up form or any other way of subscribing only asks them to confirm: the confirmation email goes, and they are subscribed (and come off the list) when they click it. Erasing someone who was simply subscribed leaves nothing behind.

Contacts nobody needs. Every night Badger erases contacts that have been inactive for the Keep inactive contacts for (months) setting (contact-retention-months, 24 by default; 0 keeps contacts for ever), but only those that meet all of these:

  • they are not subscribed: anyone with a confirmed subscription to any list is kept. Someone who unsubscribed is erased like anyone else, and their refusal is remembered on the do-not-email list (above), so they can't be signed up again by mistake. A sign-up that was never confirmed is withdrawn with the contact, and its confirmation link stops working;
  • no order has been placed with their email since the cutoff;
  • their last activity (sign-up, order, enquiry) was before the cutoff.

A contact that has an order within the period, or a subscription, is never erased by the job. The job checks again at the moment it erases each contact, and keeps one that signed up, ordered or clicked a link since the run started. Each run logs only how many contacts it looked at, erased and kept, never who.

Subscriber exports expire. The subscribers CSV contains personal data, so its file is deleted from storage 7 days after it was made and its entry shows as Expired; make a new one when you need it. The same nightly run does this, whether or not contacts are kept for ever.

Moving a shop between systems. Site transfer carries your mailing lists (their names, order and whether they show on the preferences page), merged by key into the target. It never carries contacts, consent or enquiries.

REST API

Contacts, mailing lists, contact requests, legal documents and subscriber exports can also be managed by an integration. The endpoints are under /v1/admin/messaging, tagged Contacts, Mailing Lists, Contact Requests and Legal Documents in the API reference. Like the admin screens they are for admin and superadmin tokens only (a finance token is refused with 403), and each call acts on the shop the token belongs to: a contact, list, request, stored text or export of another shop is a 404. Refusals come with the same problems body as the email campaign API: a field, a code and a message.

Call What it does Other answers
POST /contacts/search A page of contacts: q (an email address from its start, or part of a name), status (SUBSCRIBED, PENDING, UNSUBSCRIBED) on the list listKey (general if left out), includeErased, sort (created, activity, name), direction, page, size (up to 100). A POST, so a searched address never appears in a URL 422 for an unknown status, sort or direction (INVALID_VALUE) or list (UNKNOWN_LIST)
GET /contacts/{id} The contact's details and addresses, where they stand on each list, their consent history with the wording they were shown, and their contact requests 404
POST /contacts/{id}/unsubscribe Unsubscribes them from listKey, or from every list with "*". The record shows staff did it and which staff user. There is no call to subscribe someone: only the person can 404; 422 without a list or for one the shop doesn't have
POST /contacts/{id}/erase Erases the contact, as Erase this contact does. confirm must be the word erase. Returns whether it was erased now (false if it already was) and how many contact requests were deleted 404; 422 (CONFIRMATION_REQUIRED) without the word, and nothing is erased
GET /lists?includeArchived= Your mailing lists, general first, each with how many people are subscribed, waiting to confirm and unsubscribed
POST /lists Creates a list (key, name, description, showInPreferences, sortOrder) 422 (INVALID_LIST, with the reason) for a key that is taken, reserved or not allowed, or a missing or long name
GET, PUT /lists/{key} One list (archived ones included); changes its name, description, sort order and whether it is on the preferences page 404; 409 (BUILT_IN_LIST) changing general, whose wording is Site Text; 422
POST /lists/{key}/archive, /restore Archives or restores a list 404; 409 for general
POST /lists/{key}/export Requests the subscribers CSV of a list (202); poll its status 404 for an unknown or archived list; 429 while an export you requested is still being made
GET /subscriber-exports/{id} The export's status, and when its file expires 404 (also for other kinds of export)
GET /subscriber-exports/{id}/download A short-lived download link for a finished export 404; 409 (EXPORT_NOT_READY, EXPORT_EXPIRED)
GET /contact-requests?status=&page=&size= The inbox, newest first: new (the default), in_progress, closed, spam or all, with how many are new and in progress and when the oldest new one arrived 422 for another status
GET /contact-requests/{id} One request with its message and replies 404
POST /contact-requests/{id}/reply Emails a reply (body, plain text up to 10,000 characters; close: true closes the request too), as Send reply does 404; 422 (INVALID_REPLY) blank or too long; 409 (REQUEST_IS_SPAM, NO_EMAIL_ADDRESS)
POST /contact-requests/{id}/status Sets the status (NEW, IN_PROGRESS, CLOSED, SPAM); the request records who did it 404; 422 for another status
GET /legal-documents The terms of sale and privacy notice: the page and version label set, the hash of the page's text now, whether that text is kept, and the same warnings as the admin page
GET /legal-documents/{kind}/snapshots Every kept text of terms-of-sale or privacy-notice, newest first (up to 100), with how many records point at each and which one the page shows now 404 for another kind
GET /legal-documents/{kind}/snapshots/{id} One kept text, word for word 404

Contact and contact request answers include names and email addresses, as the admin pages show them; they are never written to the logs, which carry ids only. Reading the legal documents never stores a text, so looking can't create a "first shown" date. Subscriber exports follow the admin's rules: only confirmed subscribers are in the file, and it is deleted 7 days after it was made.

What is coming

Email campaigns send news and offers to your subscribers from your own domain, with unsubscribe in every email. Later phases add segments and automations such as back-in-stock, and other channels such as SMS and push notifications.

For developers

Piece Role
ContactService, ConsentService, ContactRequestService, LegalDocumentService, MessagingTopicService The only readers and writers of their collections (contact, contactEndpoint, consentRecord, consentState, consentWording, legalDocumentVersion, messagingTopic, contactRequest)
MarketingSignupService Sign-up forms and registration (signUp), the account page (grantFromAccount) and other forms such as bookings (grantFromForm): consent and, for double opt-in, the confirmation email; notes the privacy notice once the address agrees
AccountOverview, AccountConsentView The account overview's Emails and terms card: a read-only summary of CommunicationPreferencesService.forUser, linking to the Preferences tab
CustomerTools (MCP customers get) A read-only consent summary for an account, read through ContactDirectoryService (forUser, profile) like the admin's Marketing card, with the contactId to open in contacts get
MessagingLinkTokenService, MessagingLinkController Signed confirm and unsubscribe links and their pages
RecordCheckoutConsentProcessor The recordCheckoutConsent step of submitOrder; an account's own order confirms its waiting Preferences tab grants (MarketingSignupService.confirmAccountGrants, method ORDER_VERIFIED)
CrmIndexInitializer Creates the crm collections' declared indexes in apps that don't create indexes themselves (web, rest)
CustomerMessagingUpgrade The site upgrade above
LegacyConsentImportUpgrade, LegacyConsentImportService, LegacyConsentImportProcessingJob (worker) The one-time import of earlier booking consent; ConsentService.importLegacyMarketingGrant writes each grant
ContactDirectoryService The admin's and MCP's view of contacts; unsubscribing on someone's behalf, erasing a contact, and what happens to contacts when a customer account is deleted
ContactRequestInboxService Replying to a contact request by email (contactRequestReply)
CommunicationPreferencesService, CommunicationPreferencesExtension The account Preferences tab and the terms-changed notice
LegalSnapshotController /legal/{kind}/{hash}: a stored terms or privacy notice text
PrivacyNoticeUpgrade Sets the privacy notice page on an existing shop
PrivacyNotices The privacy notice link, which version and text a storefront page showed, and noting it (ConsentService.noteNoticeShown) without ever failing the page or order; used by CheckoutConsent, the sign-up tab and the cookie banner
LegalDocumentAdminController Customers, Legal documents: status, drift warnings and every kept text with its record count
ContactServiceImpl as a UserMergeParticipant Moves (or unlinks) the contact of an account merged away; ContactService.emailVerifiedForUser is the "this account proved the address" rule the account page uses
BookingConsentRecorder A booking form's marketing tick becomes a consent record, through MarketingSignupService.grantFromForm (double opt-in; always for staff)
ContactRetentionService, ContactRetentionJob (worker) The nightly erasure of inactive contacts who are not subscribed, through ContactDirectoryService.eraseInactive (re-checked, version-conditional; a refuser is suppressed OPTED_OUT first)
MarketingContactsExport The subscribers CSV (MARKETING_CONTACTS_CSV export)
MCP contacts, contactRequests Read-only tools for agents; opsInbox counts new contact requests
ContactAdminRestController, MailingListAdminRestController, ContactRequestAdminRestController, LegalDocumentAdminRestController (rest) The REST API above, spec-first in rest/docs/api-spec/api.yaml; they go through ContactDirectoryService (including listStandings, a contact's standing on each list), MessagingTopicService, ContactRequestService, ContactRequestInboxService, LegalDocumentService, ConsentService.countByDocument and ExportService, as the admin does

Indexes. The crm collections rely on unique indexes (one contact per address, one state per contact and purpose, one privacy-notice record per text). Only the worker creates the annotation-driven indexes (badger.mongo.auto-index-creation), so CrmIndexInitializer creates these in web and rest too, at start-up before the web server takes requests (about 20 round trips, each a no-op once the index exists). A new environment is therefore safe whichever app starts first. An index that can't be built (duplicates already in the collection) is logged and the app still starts. If Mongo can't be reached at all, the app logs it once and skips the rest rather than waiting out the driver's 30-second timeout for each index (as the image build's AOT training run, which has no Mongo, otherwise would); the next start or the worker creates them.

The consent log is append-only and consentState holds the latest decision per contact, purpose, channel and list, so "everyone who may get the newsletter" is a single indexed query. A marketing send must check that state is granted and confirmed. Erasing a contact removes its name and addresses but keeps its consent rows, which hold only ids.

Consent belongs to the contact, not the account. When duplicate accounts are merged, the duplicate's contact moves to the account kept (or is unlinked, keeping its email, when the kept account has a contact of its own); consent rows are never rewritten, and the kept account has to prove the address itself. Deleting a site deletes all of its contacts, endpoints, consent records, states and wordings, terms and privacy notice texts, lists, contact requests (with their replies) and import jobs, and removes its subscriber export files at once. It also deletes the shop's email campaigns in full: the campaigns and their stored renders, every message send and provider event, the do-not-email list and the sending domain (the domain and its webhook are also removed from Mailtrap, best effort, and a Mailtrap failure never stops the deletion).

The system update 2026-10-24-remove-user-keyed-consent-records removes consent entries with no contact (left by an earlier, account-keyed design on development and test databases only).

None of the contact, consent or enquiry collections are exported or imported by site transfer; the mailing lists (messagingTopic) are, merged by key.