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.
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
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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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(), andsendinblue.track(). Three things about this codebase make it more than a copy-paste of the snippet:page()explicitly, or every customer looks like they viewed one page and left.CustomerAuthContextknows who is signed in, soidentify()has an obvious place to hook in — as does clearing it on sign-out.Design questions to resolve before implementing
DEMO_MODE=truewith 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.Notes
Does the tracker load for everyone, or only after consent?
Is it enabled in QA?
Which events beyond page views?
What happens on sign-out and account deletion?
Implemented on branch
feature/56-brevo-tracker, commitac3f6e9. 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_consentfor that, and it is the reason this change is larger than it looks.The sentence customers agreed to named only email:
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_textverbatim 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:
analytics_consentcolumn, own toggle and wordingmarketing_consentas it stoodHow widening avoids retroactively broadening anyone's consent: the tracker is gated on
analytics_consent, computed on the server asmarketing_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.analyticsConsentis exported and unit-tested, because the failure mode is silent: a regression tracks people whose flag really istrue.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.
stopBrevoTrackingstops calls — it does not unloadsa.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.ymlsets 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 asQA_DB_PASSWORDand theQA_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 —
captureCartPaypalOrderanddemoCartPurchase. 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
tscclean, 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.