UC 8.5
Perks: Milestone Gifts — Flow 25 (Founding 500 Hoodie), Flow 26 (Tom James Suit). Flow 37 (Goodwill Shirt) split out to UC_10.1.8
| Field | Value |
|---|---|
| BA in Charge | Huyen Dinh |
| Date Created | 2026-10-21 |
| Version | v3 |
| Document References | Zapier Integration V7.pdf (§Flow 25/26, §Table H Perks_Registry) |
UC_8.5 — Perks: Milestone Gifts
1. Overview
| Field | Content |
|---|---|
| ID | UC_8.5 |
| Use Case | Perks: Milestone Gifts (Flow 25/26) |
| Description | When a trader reaches one of 2 milestones (Level 1 Founding Cohort or Level 10), the system checks perk inventory in PostgreSQL Perks_Registry (business name "Table H"), calls Apliiq/SES to fulfill the gift, records the Immutable Ledger entry (Flow 26 only), and atomically updates the Claimed count to avoid a race condition that would exceed Limit. Flow 37 (Goodwill Shirt, Support-initiated) is a separate use case — Ref: UC_10.1.8. |
| Zapier Flow | Flow 25 (Fulfillment — Founding 500 Hoodie) · Flow 26 (Fulfillment — Tom James Suit) |
| Table | Table H / Perks_Registry (Level, Active, Claimed, Limit, Fulfillment_Zap) — managed 100% inside PostgreSQL as the single source of truth (all reads/writes/atomic increments in this UC hit PostgreSQL directly, Ref: BR_8.5.1). The Zapier-side copy of this table (per the Tables A-J convention) is removed — there is no duplicate Table H in Zapier. |
| 3rd Party | Apliiq (merch fulfillment API — Hoodie) · AWS SES (Tom James rep email) · Klaviyo (Perk_Unlocked_General event) · Polygon/Ethers.js (Immutable Ledger — Flow 26 only) · Slack (#ops-marketing, Ref: slack_channel_usage.md) |
Trigger
- Flow 25: Ref: UC_2.8.2 §Step 7 — Perk Check — Flow 1 (New Trader Onboarding) queries PostgreSQL
Perks_RegistryforLevel = 1; ifActive == Yes AND Claimed < Limit, Flow 25 is triggered. - Flow 26: Ref: UC_8.1.1 §Stage 5 Step 3 — Perk Check — Flow 4A (Promotion) queries PostgreSQL
Perks_RegistryforNext_Level; ifActive == Yes AND Claimed < Limit, theFulfillment_Zapis triggered (Flow 26 whenNext_Level == 10). - Flow 37 (Goodwill Shirt, Support-initiated): moved to UC_10.1.8.
Pre-conditions
- The trader already has a
shirt_sizestored in theUserstable (collected at checkout, UC_2.8.2). - The trader already has a shipping address stored in the SQL DB (collected at checkout).
- Flow 25 and Flow 26: Once a perk has been claimed, there is no second claim of that same perk.
- Flow 25 only: The
product_idof the current checkout transaction is `EVAL_L1'.
Post-conditions
- If
Active == Yes AND Claimed < Limitat processing time:Claimedis incremented exactly once and the fulfillment action fires — the Apliiq order is created (Flow 25) or the rep email is sent toreps@tomjames.com(Flow 26). - If this increment is the one that causes
Claimed >= Limit(i.e. it crosses the threshold for the first time): that row'sActiveis set toNo, and a Slack alert is sent to#ops-marketing(Ref: BR_8.5.1 items 1 and 3). - If
Active == Noat processing time (limit was already reached earlier, by a prior request): the entire flow is bypassed.
2. Basic Flow — Flow 25: Fulfillment - Founding 500 Hoodie (Apliiq)
- The trigger is received from Ref: UC_2.8.2 §Step 7 — fired at checkout (
POST /execute-checkout, Flow 1 Step 7), NOT fired when the user passes the challenge or moves from SIM→Live. Prerequisite condition:product_id == "EVAL_L1"(does NOT apply toEVAL_L2/EVAL_L5— these are 2 different checkout products, unrelated to Flow 25)- AND no existing row in
perk_claimedfor(user_id, "Founding_500_Hoodie")— this perk, once claimed by a trader, can never be claimed again by the same trader - AND
Current_level = 1 - AND satisfies
Active == Yes AND Claimed < Limitin PostgreSQLPerks_Registry.
- The Middleware performs an atomic conditional update on PostgreSQL
Perks_Registryper Ref: BR_8.5.1 (increment-first). - [If the increment succeeds] → the Middleware calls the Apliiq API to place an order:
- Product: "Founding 500 Heavyweight Hoodie" (SKU stored as a test placeholder in environment variable for now — production Apliiq SKU ID to be provided by the client).
- Options:
shirt_sizeis mapped from theUserstable via the static size-mapping dictionary (Ref: BR_8.5.2) to the Apliiq size parameter. - Recipient: Trader Name/Address from the SQL DB.
- Claim is now locked immediately once the Apliiq order call is fired (i.e. the fulfillment action has actually been triggered, not merely the PostgreSQL increment), the Middleware inserts a row
(user_id, "Founding_500_Hoodie")intoperk_claimed— this perk can never be claimed by this trader again, regardless of any later rebuy/reset.
- The Middleware sends the
Perk_Unlocked_Generalevent to Klaviyo with payload:user_name,user_email,perk_name("Founding 500 Heavyweight Hoodie"),new_level="Associate Track".
2.1 Exceptional Flow — Flow 25
-
[If
product_id != "EVAL_L1"OR a row already exists inperk_claimedfor(user_id, "Founding_500_Hoodie")]- Context: this is a failure at the trigger condition itself (step 1)
- System behavior: Flow 25 is not triggered — no PostgreSQL
Perks_Registryquery, no increment, no alert of any kind, not even to Ops/Marketing (silent skip). - Outcome: nothing in PostgreSQL is ever touched for this attempt.
-
[If the increment at step 2 fails because
Limitwas already reached by an earlier request]- Context: all of step 1's trigger conditions were true at check time, but by the moment the atomic
UPDATEin step 2 actually runs,Claimedhas already reachedLimit— other request got there first and already flippedActivetoNo. - Example:
Limit = 500. Trader A's request is the 500th one — A's ownUPDATEsucceeds, incrementsClaimedfrom 499 to 500, and flipsActiveto'No'in that same statement. A still gets the hoodie — that is the normal success path (step 2/3 above), not this exceptional case. Trader B's request arrives right after, findsActivealready'No'. - System behavior: bypass the rest of the flow for B — no Apliiq order, no Klaviyo event, no
perk_claimedinsert. No Slack alert either — the "Limit Reached" Slack alert fires only once, for the one request that actually flipsActivetoNo(A, in the example above); every later request that fails this same check (B, C, D...) is a silent no-op with no alert of its own. - Outcome: the perk was never actually fulfilled, so it remains unclaimed for this trader.
- Context: all of step 1's trigger conditions were true at check time, but by the moment the atomic
-
[If the increment at step 2 succeeds but the Apliiq API call at step 3 fails] — Ref: BR_8.5.3.
- System behavior: retry the Apliiq call up to 3 times. If all 3 attempts fail: decrement the
Claimedcounter onPerks_Registryback by 1 (rollback the increment from step 2, so the slot is not lost from the pool); create a Freshdesk ticket for Operations withuser_id,admin_id,action_type("Fulfillment - Founding 500 Hoodie"), andtimestamp; do not insert intoperk_claimed; do not show any error to the trader; the main checkout flow (Flow 1) continues normally regardless of this outcome.
- System behavior: retry the Apliiq call up to 3 times. If all 3 attempts fail: decrement the
3. Basic Flow — Flow 26: Fulfillment - Tom James Suit
-
The trigger is received from UC_8.1.1 §Stage 5 Step 3 when
Next_Level = 10satisfiesActive == Yes AND Claimed < LimitAND no existing row inperk_claimedfor(user_id, "Tom_James_Suit") -
The Middleware performs an atomic conditional update on PostgreSQL
Perks_Registryper Ref: BR_8.5.1 (increment-first — runs BEFORE sending the email/writing the ledger, to avoid sending the email past theLimitdue to a race condition). -
[If the increment succeeds] → the Middleware sends an email via Amazon SES:
- To:
reps@tomjames.comis a placeholder, stored in environment variableTOM_JAMES_REP_EMAIL. For QA/UAT, this variable is set to a SotaTek test email address so the Flow 26 email can be tested without emailing Tom James; for production, the client will provide the live recipient address. - Subject: "New Level 10 Trader - Suit Fitting Request"
- Body: "New Level 10 Trader: [First Name + Last Name] Location: [City, State] Contact: [Phone/Email] Budget Code: STACK-SUIT-L10-[user_id] Please reach out to schedule." — note: this email does not include
shirt_size; Tom James measures the trader in person for the custom suit. - Claim is now locked in: immediately once the SES email has actually been sent, the Middleware inserts a row
(user_id, "Tom_James_Suit")intoperk_claimed— this perk can never be claimed by this trader again, even ifcurrent_levellater drops below 10 and rises back to 10
- To:
-
The Middleware sends a Slack alert to
#ops-marketing: "Suit Claimed by [Trader]". -
Immutable Ledger Logging: the Middleware calls
POST /api/ledger/record:Input field Value trader_idthe user's ID metric_category"Luxury Perk Unlocked" metric_value"Bespoke Suit Consultation Initiated" dateToday receipt_hashSES message ID (unique message ID generated by Amazon SES when the email in step 3 is sent) tx_hashis NOT an input — it is an output that the Middleware generates itself after callingcontract.recordMetric()via Ethers.js, submitting through the Alchemy RPC to the Polygon smart contract, waiting for confirmation, and then writingtx_hash(the Polygon transaction hash) into PostgreSQL.
3.1 Exceptional Flow — Flow 26
- [If a row already exists in
perk_claimedfor(user_id, "Tom_James_Suit")]- Context: failure at the trigger condition itself (step 1).
- System behavior: Flow 26 is not triggered — no PostgreSQL
Perks_Registryquery, no increment, no alert of any kind (silent skip). - Outcome: nothing in PostgreSQL is touched for this attempt.
- [If the increment at step 2 fails because
Limitwas already reached by an earlier request]- Context: same race-condition pattern as Flow 25 §2.1 — the trigger condition in step 1 passed, but by the moment the atomic
UPDATEin step 2 actually runs,Claimedhas already reachedLimit— another request got there first and already flippedActivetoNo. - System behavior: bypass the rest of the flow for this request — no SES email, no Immutable Ledger write, no
perk_claimedinsert. No Slack alert either — the "Limit Reached" Slack alert fires only once, for the one earlier request that actually flippedActivetoNo; every later request that fails this same check is a silent no-op with no alert of its own. - Outcome: the perk was never actually fulfilled, so it remains unclaimed for this trader.
- Context: same race-condition pattern as Flow 25 §2.1 — the trigger condition in step 1 passed, but by the moment the atomic
- [If the increment at step 2 succeeds but the SES email call at step 3 fails] — Ref: BR_8.5.3.
- System behavior: retry the SES send up to 3 times. If all 3 attempts fail: decrement the
Claimedcounter onPerks_Registryback by 1 (rollback the increment from step 2, so the slot is not lost from the pool); create a Freshdesk ticket for Operations withuser_id,admin_id,action_type("Fulfillment - Tom James Suit"), andtimestamp; do not insert intoperk_claimed; do not show any error to the trader; the main promotion flow (Flow 4A) continues normally regardless of this outcome.
- System behavior: retry the SES send up to 3 times. If all 3 attempts fail: decrement the
Flow 37 (Fulfillment - Goodwill Shirt + BPS Admin Endpoint "Send Goodwill Merch") has been split out into its own use case — Ref: UC_10.1.8.
4. Business Rules
BR_8.5.1: Perk Claim Atomic Increment Pattern (PostgreSQL Perks_Registry)
Applies to both flows (25/26) when updating the Claimed count on PostgreSQL Perks_Registry (business name "Table H"). The same pattern also applies to Flow 37 — Ref: UC_10.1.8 §BR_10.1.8.1.
Which physical table this hits: the increment below runs against the PostgreSQL Perks_Registry table.
-
Atomic conditional increment: the Middleware perform the increment (Claimed = current_claimed_number + 1) and the limit check (Claimed < limit).
- If the increment succeeds (the trader's request was within the limit) → continue with the corresponding fulfillment flow (Apliiq order / email / ledger write).
- If the increment fails (the Limit was already reached earlier, by another request) → bypass the entire fulfillment flow — no retry, no error shown to the trader.
-
Execution order (increment-first): incrementing PostgreSQL
Perks_Registryalways runs BEFORE the dependent actions (Apliiq order, SES email, Klaviyo event, Immutable Ledger write). -
Limit Reached Alert: if the increment causes
Claimed = Limit(i.e.Activehas just flipped toNo), the Middleware sends a Slack alert to#ops-marketing(C0BQX3AV679):Flow Message Flow 25 "Founding 500 Hoodie Limit Reached!" Flow 26 "Tom James Suit Limit Reached!" Both messages route to the same actual channel
#ops-marketing(Flow 37's equivalent alert is documented in Ref: UC_10.1.8 §BR_10.1.8.1 item 3).
BR_8.5.2: Shirt Size Mapping (Apliiq)
-
Tom James (Flow 26): does not use
shirt_sizeat all — Tom James measures the trader in person for the custom suit. -
Apliiq (Flow 25 & Flow 37 — Ref: UC_10.1.8 §2.2): the backend config holds a static mapping dictionary from the 5 dropdown sizes to the Apliiq size/variant string:
Dropdown size Apliiq variant S S M M L L XL XL XXL 2XL
BR_8.5.3: Perk Fulfillment API Failure / Retry Handling (Apliiq, SES)
Applies to Flow 25 (Apliiq), Flow 26 (SES), and Flow 37 (Apliiq, Ref: UC_10.1.8 §2.3) — runs only after the Claimed increment (Ref: BR_8.5.1) has already succeeded and the downstream fulfillment call (Apliiq order / SES email) itself fails.
- Retry: retry the fulfillment API call up to 3 times.
- If all 3 attempts fail: decrement the
Claimedcounter onPerks_Registryback by 1 (rollback the increment, so the slot is not lost from the pool), and create a Freshdesk ticket for Operations withuser_id,admin_id,action_type, andtimestamp. - Do not mark the perk as claimed — no row is inserted into
perk_claimed. - Do not show any error to the trader.
- Do not block the main flow — the checkout flow (Flow 25) or promotion flow (Flow 26) continues normally regardless of this outcome.
Changelog
| Date | Version | Updated item | Before | After | Notes |
|---|---|---|---|---|---|
| 2026-10-21 | v1 | Initial document | — | — | — |
| 2026-10-21 | v1.9 | Removed BR_8.5.2 (Bypass Behaviour when Active = No) and BR_8.5.3 (Duplicate Claim Prevention) as standalone sections | Dedicated BR_8.5.2/BR_8.5.3 sections | Core content folded inline at each usage point (trigger conditions, exceptional flows) | Content preserved, section count reduced |
| 2026-10-21 | v1.9 | Removed BR_8.5.4 (Flow 25 Trigger Condition — EVAL_L1 Only, Exclude Rebuy & Reset) as standalone section | Dedicated BR_8.5.4 section | Core rule (exclude RESET/REBUY) + open schema-review item folded into Pre-conditions | Open item preserved, section count reduced |
| 2026-10-21 | v2 | Split Flow 37 (Goodwill Shirt + BPS Admin Endpoint "Send Goodwill Merch") out into its own use case | Flow 37 content embedded in UC_8.5 (§4, part of BR_8.5.1) | Flow 37 moved to standalone UC_10.1.8; UC_8.5 now covers Flow 25/26 only | Content preserved as reviewed, cross-refs added |
| 2026-10-29 | v3 | Table H confirmed PostgreSQL-only (Zapier-side copy removed, no sync cron); added Apliiq SKU test-placeholder note (Flow 25); Tom James email moved to env var TOM_JAMES_REP_EMAIL + confirmed no shirt_size (Flow 26); added new BR_8.5.2 (Shirt Size Mapping dictionary) and BR_8.5.3 (Perk Fulfillment API Failure/Retry with Claimed counter rollback + Freshdesk ticket) | §Overview Table row described async Zapier copy; SKU/email hardcoded without env var note; no retry/failure handling | See updated §Overview Table row, §2 step 3, §3 step 3, §2.1, §3.1, new BR_8.5.2/BR_8.5.3 | Client QnA 2026-10-29 |