StackTrading Docs

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

FieldValue
BA in ChargeHuyen Dinh
Date Created2026-10-21
Versionv3
Document ReferencesZapier Integration V7.pdf (§Flow 25/26, §Table H Perks_Registry)

UC_8.5 — Perks: Milestone Gifts

1. Overview

FieldContent
IDUC_8.5
Use CasePerks: Milestone Gifts (Flow 25/26)
DescriptionWhen 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 FlowFlow 25 (Fulfillment — Founding 500 Hoodie) · Flow 26 (Fulfillment — Tom James Suit)
TableTable 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 PartyApliiq (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_Registry for Level = 1; if Active == 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_Registry for Next_Level; if Active == Yes AND Claimed < Limit, the Fulfillment_Zap is triggered (Flow 26 when Next_Level == 10).
  • Flow 37 (Goodwill Shirt, Support-initiated): moved to UC_10.1.8.

Pre-conditions

  • The trader already has a shirt_size stored in the Users table (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_id of the current checkout transaction is `EVAL_L1'.

Post-conditions

  • If Active == Yes AND Claimed < Limit at processing time: Claimed is incremented exactly once and the fulfillment action fires — the Apliiq order is created (Flow 25) or the rep email is sent to reps@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's Active is set to No, and a Slack alert is sent to #ops-marketing (Ref: BR_8.5.1 items 1 and 3).
  • If Active == No at 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)

  1. 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 to EVAL_L2/EVAL_L5 — these are 2 different checkout products, unrelated to Flow 25)
    • AND no existing row in perk_claimed for (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 < Limit in PostgreSQL Perks_Registry.
  2. The Middleware performs an atomic conditional update on PostgreSQL Perks_Registry per Ref: BR_8.5.1 (increment-first).
  3. [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_size is mapped from the Users table 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") into perk_claimed — this perk can never be claimed by this trader again, regardless of any later rebuy/reset.
  4. The Middleware sends the Perk_Unlocked_General event 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 in perk_claimed for (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_Registry query, 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 Limit was 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 UPDATE in step 2 actually runs, Claimed has already reached Limit — other request got there first and already flipped Active to No.
    • Example: Limit = 500. Trader A's request is the 500th one — A's own UPDATE succeeds, increments Claimed from 499 to 500, and flips Active to '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, finds Active already 'No'.
    • System behavior: bypass the rest of the flow for B — no Apliiq order, no Klaviyo event, no perk_claimed insert. No Slack alert either — the "Limit Reached" Slack alert fires only once, for the one request that actually flips Active to No (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.
  • [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 Claimed counter on Perks_Registry back by 1 (rollback the increment from step 2, so the slot is not lost from the pool); create a Freshdesk ticket for Operations with user_id, admin_id, action_type ("Fulfillment - Founding 500 Hoodie"), and timestamp; do not insert into perk_claimed; do not show any error to the trader; the main checkout flow (Flow 1) continues normally regardless of this outcome.

3. Basic Flow — Flow 26: Fulfillment - Tom James Suit

  1. The trigger is received from UC_8.1.1 §Stage 5 Step 3 when Next_Level = 10 satisfies Active == Yes AND Claimed < Limit AND no existing row in perk_claimed for (user_id, "Tom_James_Suit")

  2. The Middleware performs an atomic conditional update on PostgreSQL Perks_Registry per Ref: BR_8.5.1 (increment-first — runs BEFORE sending the email/writing the ledger, to avoid sending the email past the Limit due to a race condition).

  3. [If the increment succeeds] → the Middleware sends an email via Amazon SES:

    • To: reps@tomjames.com is a placeholder, stored in environment variable TOM_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") into perk_claimed — this perk can never be claimed by this trader again, even if current_level later drops below 10 and rises back to 10
  4. The Middleware sends a Slack alert to #ops-marketing: "Suit Claimed by [Trader]".

  5. Immutable Ledger Logging: the Middleware calls POST /api/ledger/record:

    Input fieldValue
    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_hash is NOT an input — it is an output that the Middleware generates itself after calling contract.recordMetric() via Ethers.js, submitting through the Alchemy RPC to the Polygon smart contract, waiting for confirmation, and then writing tx_hash (the Polygon transaction hash) into PostgreSQL.

3.1 Exceptional Flow — Flow 26

  • [If a row already exists in perk_claimed for (user_id, "Tom_James_Suit")]
    • Context: failure at the trigger condition itself (step 1).
    • System behavior: Flow 26 is not triggered — no PostgreSQL Perks_Registry query, 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 Limit was 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 UPDATE in step 2 actually runs, Claimed has already reached Limit — another request got there first and already flipped Active to No.
    • System behavior: bypass the rest of the flow for this request — no SES email, no Immutable Ledger write, no perk_claimed insert. No Slack alert either — the "Limit Reached" Slack alert fires only once, for the one earlier request that actually flipped Active to No; 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.
  • [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 Claimed counter on Perks_Registry back by 1 (rollback the increment from step 2, so the slot is not lost from the pool); create a Freshdesk ticket for Operations with user_id, admin_id, action_type ("Fulfillment - Tom James Suit"), and timestamp; do not insert into perk_claimed; do not show any error to the trader; the main promotion flow (Flow 4A) continues normally regardless of this outcome.

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.

  1. 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.
  2. Execution order (increment-first): incrementing PostgreSQL Perks_Registry always runs BEFORE the dependent actions (Apliiq order, SES email, Klaviyo event, Immutable Ledger write).

  3. Limit Reached Alert: if the increment causes Claimed = Limit (i.e. Active has just flipped to No), the Middleware sends a Slack alert to #ops-marketing (C0BQX3AV679):

    FlowMessage
    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_size at 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 sizeApliiq variant
    SS
    MM
    LL
    XLXL
    XXL2XL

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.

  1. Retry: retry the fulfillment API call up to 3 times.
  2. If all 3 attempts fail: decrement the Claimed counter on Perks_Registry back by 1 (rollback the increment, so the slot is not lost from the pool), and create a Freshdesk ticket for Operations with user_id, admin_id, action_type, and timestamp.
  3. Do not mark the perk as claimed — no row is inserted into perk_claimed.
  4. Do not show any error to the trader.
  5. Do not block the main flow — the checkout flow (Flow 25) or promotion flow (Flow 26) continues normally regardless of this outcome.

Changelog

DateVersionUpdated itemBeforeAfterNotes
2026-10-21v1Initial document———
2026-10-21v1.9Removed BR_8.5.2 (Bypass Behaviour when Active = No) and BR_8.5.3 (Duplicate Claim Prevention) as standalone sectionsDedicated BR_8.5.2/BR_8.5.3 sectionsCore content folded inline at each usage point (trigger conditions, exceptional flows)Content preserved, section count reduced
2026-10-21v1.9Removed BR_8.5.4 (Flow 25 Trigger Condition — EVAL_L1 Only, Exclude Rebuy & Reset) as standalone sectionDedicated BR_8.5.4 sectionCore rule (exclude RESET/REBUY) + open schema-review item folded into Pre-conditionsOpen item preserved, section count reduced
2026-10-21v2Split Flow 37 (Goodwill Shirt + BPS Admin Endpoint "Send Goodwill Merch") out into its own use caseFlow 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 onlyContent preserved as reviewed, cross-refs added
2026-10-29v3Table 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 handlingSee updated §Overview Table row, §2 step 3, §3 step 3, §2.1, §3.1, new BR_8.5.2/BR_8.5.3Client QnA 2026-10-29

On this page