Implement the Brevo Tracker #56

Closed
opened 2026-08-18 15:41:28 -05:00 by bermudalamb · 2 comments
Owner

Goal

Install Brevo's web tracking script so page views and customer events reach Brevo and can drive automations. The Marketing Automation key comes from the automation parameters page.

Scope

The tracker is a small script that exposes sendinblue.page(), sendinblue.identify(), and sendinblue.track(). Three things about this codebase make it more than a copy-paste of the snippet:

  • The storefront is a single-page app. There is no full page load between routes, so the snippet's implicit page view fires exactly once per session. Route changes have to call page() explicitly, or every customer looks like they viewed one page and left.
  • Identity is already modelled. CustomerAuthContext knows who is signed in, so identify() has an obvious place to hook in — as does clearing it on sign-out.
  • Consent is already recorded carefully. Marketing consent and favorite alerts each store a flag, a timestamp, and the exact wording shown. A tracker that reports every visitor's browsing by default is a different category of data collection from those two, and should not be bolted on without the same deliberateness.

Design questions to resolve before implementing

  • Does the tracker load for everyone, or only after consent? Loading it for every visitor is the default the snippet assumes and gives complete data. Gating it behind consent matches how this project has treated every other piece of customer data so far, and matches what the privacy policy currently claims. These give materially different implementations, so it should be settled first.
  • Is it enabled in QA? QA runs DEMO_MODE=true with no PayPal credentials and no SMTP precisely so it cannot reach live services or email anyone. Sending QA browsing into the same Brevo account would pollute production data with test traffic. Options are a separate key per environment, or no key at all in QA.
  • Which events beyond page views? Natural candidates are add-to-cart, checkout completed, and favoriting — all of which already have a single well-defined place in the code where they succeed.
  • What happens on sign-out and account deletion? The delete-my-account flow removes the customer and disconnects their orders. Whether that should also stop or unlink Brevo tracking is a decision, not an implementation detail.

Notes

  • The Marketing Automation key is configuration, not code — it belongs in the environment alongside the other per-environment settings, not committed. It is not a secret in the way an API key is (it ships to the browser), but it does differ per environment.
  • The privacy policy will need reviewing against whatever is decided here.
## Goal Install Brevo's web tracking script so page views and customer events reach Brevo and can drive automations. The Marketing Automation key comes from the [automation parameters page](https://app.brevo.com/automation/parameters). ## Scope The tracker is a small script that exposes `sendinblue.page()`, `sendinblue.identify()`, and `sendinblue.track()`. Three things about this codebase make it more than a copy-paste of the snippet: - **The storefront is a single-page app.** There is no full page load between routes, so the snippet's implicit page view fires exactly once per session. Route changes have to call `page()` explicitly, or every customer looks like they viewed one page and left. - **Identity is already modelled.** `CustomerAuthContext` knows who is signed in, so `identify()` has an obvious place to hook in — as does clearing it on sign-out. - **Consent is already recorded carefully.** Marketing consent and favorite alerts each store a flag, a timestamp, and the exact wording shown. A tracker that reports every visitor's browsing by default is a different category of data collection from those two, and should not be bolted on without the same deliberateness. ## Design questions to resolve before implementing - **Does the tracker load for everyone, or only after consent?** Loading it for every visitor is the default the snippet assumes and gives complete data. Gating it behind consent matches how this project has treated every other piece of customer data so far, and matches what the privacy policy currently claims. These give materially different implementations, so it should be settled first. - **Is it enabled in QA?** QA runs `DEMO_MODE=true` with no PayPal credentials and no SMTP precisely so it cannot reach live services or email anyone. Sending QA browsing into the same Brevo account would pollute production data with test traffic. Options are a separate key per environment, or no key at all in QA. - **Which events beyond page views?** Natural candidates are add-to-cart, checkout completed, and favoriting — all of which already have a single well-defined place in the code where they succeed. - **What happens on sign-out and account deletion?** The delete-my-account flow removes the customer and disconnects their orders. Whether that should also stop or unlink Brevo tracking is a decision, not an implementation detail. ## Notes - The Marketing Automation key is configuration, not code — it belongs in the environment alongside the other per-environment settings, not committed. It is not a secret in the way an API key is (it ships to the browser), but it does differ per environment. - The privacy policy will need reviewing against whatever is decided here.
Author
Owner

Does the tracker load for everyone, or only after consent?

  • It is gated behind consent so that it matches how this project has treated every other piece of customer data so far, and matches what the privacy policy currently claims.

Is it enabled in QA?

  • Send QA browsing into the same Brevo account would pollute production data with test traffic. Options are a separate key per environment.

Which events beyond page views?

  • Add-to-cart
  • checkout completed,
  • favoriting

What happens on sign-out and account deletion?

  • Brevo tracking should stop.
Does the tracker load for everyone, or only after consent? - It is gated behind consent so that it matches how this project has treated every other piece of customer data so far, and matches what the privacy policy currently claims. Is it enabled in QA? - Send QA browsing into the same Brevo account would pollute production data with test traffic. Options are a separate key per environment. Which events beyond page views? - Add-to-cart - checkout completed, - favoriting What happens on sign-out and account deletion? - Brevo tracking should stop.
bermudalamb added this to the Implement the Brevo Tracker project 2026-08-18 17:32:06 -05:00
bermudalamb self-assigned this 2026-08-18 17:32:16 -05:00
Author
Owner

Implemented on branch feature/56-brevo-tracker, commit ac3f6e9. The four design questions above were already answered in August and this follows them. Two further questions came up during implementation; both are recorded here with the options that were rejected.

Question 1: what does "gated behind consent" actually gate on?

The answer above says gate the tracker on consent. Implementing it surfaced a problem with using marketing_consent for that, and it is the reason this change is larger than it looks.

The sentence customers agreed to named only email:

I want to receive occasional emails about new one-of-a-kind items from Redefined Designs. I can unsubscribe at any time.

Gating a behavioural tracker on that flag would treat "email me about new items" as authorisation to send someone's browsing to a third party, which it does not say. This project stores marketing_consent_text verbatim against each customer precisely so a record says what that customer was shown, so reusing the flag for a different purpose would have defeated the mechanism built to prevent exactly this.

Options considered:

Option Why not / why
Separate analytics_consent column, own toggle and wording Rejected as more surface than needed — a migration, an account-page control and a second consent record, when the existing one can carry both if the wording says so.
Widen the marketing consent wording Chosen. One consent, one toggle, and the stored-text mechanism already distinguishes who agreed to what.
Use marketing_consent as it stood Rejected. Fastest, but it treats consent as covering something the customer never read.

How widening avoids retroactively broadening anyone's consent: the tracker is gated on analytics_consent, computed on the server as marketing_consent && marketing_consent_text === MARKETING_CONSENT_TEXT. Customers who agreed to the old wording keep their email consent and are not tracked until they re-consent from the account page. A boolean alone could not tell the two populations apart.

analyticsConsent is exported and unit-tested, because the failure mode is silent: a regression tracks people whose flag really is true.

Question 2: the privacy policy described none of this

The published policy's "What we collect" listed only account and purchase data, with no analytics or third-party section at all.

Options considered: ship now and file a follow-up (rejected — leaves a window where the site collects data the policy does not describe); block on a reviewed policy (rejected as heavier than this warrants); draft it in this change (chosen).

The new section states the limits honestly: withdrawing consent stops further reporting, but anything already sent stays with Brevo, and a script already injected cannot be un-injected. stopBrevoTracking stops calls — it does not unload sa.js.

On the remaining answers

QA isolation is by construction, not by discipline. The key is per-environment and the tracker never loads without one. docker-compose.qa.yml sets an empty literal with no stack variable behind it, so nothing can inherit a value from the host or be pasted in from production's stack — the same reasoning as QA_DB_PASSWORD and the QA_SMTP_* names beside it.

Events are reported from the API layer, not the UI call sites, so no caller can add to the cart or favorite an item without it being counted. Each fires only after the response was accepted, so a refused add is not reported as one. The two checkout completions each name their processor: a demo purchase charges nothing, and counting it as a sale would overstate revenue.

One deviation worth flagging: the issue said each event has "a single well-defined place where it succeeds". That holds for add-to-cart and favoriting, but checkout has twocaptureCartPaypalOrder and demoCartPurchase. Both are instrumented.

Only favoriting is tracked, not un-favoriting; the issue asked for favoriting as an interest signal and removal is a different question.

Verification

Backend tsc clean, both lint suites 0 errors with no new warnings, 474 unit tests passing across 33 suites, frontend production build green including the compose-environment guard.

Not verified: integration and e2e, which need a database and a Node version this machine does not have active; and no real Brevo key was exercised, so the tracker has never been observed reporting into an actual account. The first QA run with a key is the real test.

Implemented on branch `feature/56-brevo-tracker`, commit `ac3f6e9`. The four design questions above were already answered in August and this follows them. Two further questions came up during implementation; both are recorded here with the options that were rejected. ## Question 1: what does "gated behind consent" actually gate on? The answer above says gate the tracker on consent. Implementing it surfaced a problem with using `marketing_consent` for that, and it is the reason this change is larger than it looks. The sentence customers agreed to named **only email**: > I want to receive occasional emails about new one-of-a-kind items from Redefined Designs. I can unsubscribe at any time. Gating a behavioural tracker on that flag would treat "email me about new items" as authorisation to send someone's browsing to a third party, which it does not say. This project stores `marketing_consent_text` verbatim against each customer precisely so a record says what that customer was shown, so reusing the flag for a different purpose would have defeated the mechanism built to prevent exactly this. **Options considered:** | Option | Why not / why | | --- | --- | | Separate `analytics_consent` column, own toggle and wording | Rejected as more surface than needed — a migration, an account-page control and a second consent record, when the existing one can carry both if the wording says so. | | **Widen the marketing consent wording** | **Chosen.** One consent, one toggle, and the stored-text mechanism already distinguishes who agreed to what. | | Use `marketing_consent` as it stood | Rejected. Fastest, but it treats consent as covering something the customer never read. | **How widening avoids retroactively broadening anyone's consent:** the tracker is gated on `analytics_consent`, computed on the server as `marketing_consent && marketing_consent_text === MARKETING_CONSENT_TEXT`. Customers who agreed to the old wording keep their email consent and are **not** tracked until they re-consent from the account page. A boolean alone could not tell the two populations apart. `analyticsConsent` is exported and unit-tested, because the failure mode is silent: a regression tracks people whose flag really is `true`. ## Question 2: the privacy policy described none of this The published policy's "What we collect" listed only account and purchase data, with no analytics or third-party section at all. **Options considered:** ship now and file a follow-up (rejected — leaves a window where the site collects data the policy does not describe); block on a reviewed policy (rejected as heavier than this warrants); **draft it in this change** (chosen). The new section states the limits honestly: withdrawing consent stops further reporting, but anything already sent stays with Brevo, and a script already injected cannot be un-injected. `stopBrevoTracking` stops calls — it does not unload `sa.js`. ## On the remaining answers **QA isolation is by construction, not by discipline.** The key is per-environment and the tracker never loads without one. `docker-compose.qa.yml` sets an empty literal with no stack variable behind it, so nothing can inherit a value from the host or be pasted in from production's stack — the same reasoning as `QA_DB_PASSWORD` and the `QA_SMTP_*` names beside it. **Events are reported from the API layer**, not the UI call sites, so no caller can add to the cart or favorite an item without it being counted. Each fires only after the response was accepted, so a refused add is not reported as one. The two checkout completions each name their processor: a demo purchase charges nothing, and counting it as a sale would overstate revenue. **One deviation worth flagging:** the issue said each event has "a single well-defined place where it succeeds". That holds for add-to-cart and favoriting, but checkout has **two** — `captureCartPaypalOrder` and `demoCartPurchase`. Both are instrumented. Only favoriting is tracked, not un-favoriting; the issue asked for favoriting as an interest signal and removal is a different question. ## Verification Backend `tsc` clean, both lint suites 0 errors with no new warnings, **474 unit tests passing across 33 suites**, frontend production build green including the compose-environment guard. **Not verified:** integration and e2e, which need a database and a Node version this machine does not have active; and no real Brevo key was exercised, so the tracker has never been observed reporting into an actual account. The first QA run with a key is the real test.
bermudalamb moved this to Review in Implement the Brevo Tracker on 2026-09-09 13:32:53 -05:00
bermudalamb moved this to Ready for Release in Implement the Brevo Tracker on 2026-09-09 13:32:58 -05:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bermudalamb/redefined-designs#56