From 4aa9c91bc769ec5e2de5b2f36fc1df128001f87a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mats=20T=C3=B6rnberg?= Date: Wed, 5 Aug 2026 15:07:39 +0200 Subject: [PATCH] newsletter: dev/deploy env wiring (PROFILE_URL, log sender), CampaignEvent.URL, plan + status docs --- event/event.go | 56 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/event/event.go b/event/event.go index 0725231..7b1aa91 100644 --- a/event/event.go +++ b/event/event.go @@ -72,15 +72,26 @@ const ( TrackCart Type = "track.cart" // 3,4 — add to / remove from cart TrackImpression Type = "track.impression" // 5 — impression TrackAction Type = "track.action" // 6 — custom action (wishlist, etc.) - TrackPageView Type = "track.page_view" - TrackPageEngaged Type = "track.page_engaged" - TrackPageAction Type = "track.page_action" - TrackExperiment Type = "track.experiment" -// TrackPurchase — cut 23. Post-checkout funnel closure; emitted by -// event_handlers.go::TrackPurchase on a purchase request. Replaces -// 5 hand-written `pevent.Type("track.purchase")` literals with this -// typed constant. - TrackPurchase Type = "track.purchase" + TrackPageView Type = "track.page_view" + TrackPageEngaged Type = "track.page_engaged" + TrackPageAction Type = "track.page_action" + TrackExperiment Type = "track.experiment" + // TrackPurchase — cut 23. Post-checkout funnel closure; emitted by + // event_handlers.go::TrackPurchase on a purchase request. Replaces + // 5 hand-written `pevent.Type("track.purchase")` literals with this + // typed constant. + TrackPurchase Type = "track.purchase" + + // Newsletter campaign attribution (review 3.8). Producer: the order + // service's MailerSend webhook receiver (cmd/order/newsletter-webhook.go) + // when MailerSend reports an open / click for a newsletter send. Consumer: + // slask-tracking (pkg/events/rabbit-tracking.go) — feeds per-send + // open/click rates for the backoffice delivery log. Payload is an + // event.CampaignEvent. Wire routing keys `track.campaign_open` / + // `track.campaign_click` ride the existing `track.*` pattern on the + // catalog exchange. + TrackCampaignOpen Type = "track.campaign_open" + TrackCampaignClick Type = "track.campaign_click" // Wishlist attribution (W6d of docs/plan-wishlist-favorites.md). // Producers: profile service (share-page read) and cart service @@ -105,13 +116,31 @@ const ( // `track.*` pattern — so slask-tracking's existing `track.*` binding // does not pick them up. Payload: event.WishlistMutationEvent (profile) // / event.CartToWishlistEvent (cart). See docs/amqp-topics.md. - WishlistCreated Type = "wishlist.created" // explicit user-created list (not the implicit default materialisation) - WishlistItemAdded Type = "wishlist.item_added" // one per SKU added (single-add and batch paths) + WishlistCreated Type = "wishlist.created" // explicit user-created list (not the implicit default materialisation) + WishlistItemAdded Type = "wishlist.item_added" // one per SKU added (single-add and batch paths) WishlistItemRemoved Type = "wishlist.item_removed" - WishlistShared Type = "wishlist.shared" // share (re)generated — token itself never leaves the service - CartToWishlist Type = "cart.to_wishlist" // cart → list conversion (cart service) + WishlistShared Type = "wishlist.shared" // share (re)generated — token itself never leaves the service + CartToWishlist Type = "cart.to_wishlist" // cart → list conversion (cart service) ) +// CampaignEvent is the payload for the newsletter campaign attribution +// events (TrackCampaignOpen / TrackCampaignClick). Deliberately pseudonymous +// (C5): it carries the send id (the `newsletter:{sendID}` MailerSend tag +// parsed from the webhook) and a sha256 hex hash of the recipient email, +// never the address itself. At is unix seconds (MailerSend's created_at; +// falls back to arrival time when absent). +// +// URL is the link that was clicked (TrackCampaignClick only): the webhook +// receiver stamps the MailerSend click URL so slask-tracking can aggregate +// top-clicked links per send. Empty for opens and for clicks whose payload +// carried no URL. +type CampaignEvent struct { + SendID string `json:"sendId"` + EmailHash string `json:"emailHash"` + At int64 `json:"at,omitempty"` + URL string `json:"url,omitempty"` +} + // WishlistEvent is the payload for the wishlist attribution events // (WishlistViewed / WishlistToCart). Deliberately lean: list id + catalog // item ids only — no owner id, no list name, no share token, so the @@ -184,7 +213,6 @@ func IsKnownType(s string) bool { return false } - // Event is the one envelope. Payload is per-Type, agreed between producer and // consumer (json.RawMessage, not a god-struct). A nil Payload means "the subject // changed; fetch the latest state."