RestaurantBrain feasibility study

#r06 — The CRM: guest identity, loyalty, marketing, privacy

Scope: guest CRM/loyalty/marketing layer of RestaurantBrain. The POS is treated here purely as the data-capture and redemption device. POS internals, payments, fiscalisation and hardware are covered in sibling docs; where a fiscal or payments constraint reshapes CRM design I call it out inline.

Bottom line: the CRM is the only part of this product that is defensible, and it is also the part where a small team can realistically out-build incumbents — but only because incumbents are weak at exactly two things: (1) loyalty that works correctly while the terminal is offline, and (2) holdout-based revenue attribution. Everything else in CRM is commodity. The CRM domain alone is ~55 engineer-months to a sellable v1. That number, combined with the POS build, is the single biggest argument against scenario S1 (bootstrapped) attempting both.


#1. Identity resolution

#1.1 The identifier inventory

Every identifier a restaurant can plausibly touch, with its real properties:

IdentifierSourceStabilityUniquenessLegally usable as a match key?Notes
Phone (E.164)POS ask, SMS receipt, reservation, delivery, appHigh~1 person, but shared in couples/familiesYes (contract or consent)Primary key candidate. Normalise with libphonenumber, store E.164 + country of origin
Email (normalised)app, web order, wifi, receiptHigh~1 person; gmail dot/plus aliasingYesNormalise: lowercase, strip +tag, strip dots for gmail.com only
Internal loyalty IDour issuanceAbsolute1:1YesBarcode/QR on wallet pass; never reuse
Card PAR (EMVCo Payment Account Reference, 29-char alnum)acquirer/PSP in auth responseHigh — survives re-issue in many cases, links PAN + Apple Pay DPAN + card-on-file token to one funding account [verified]Very highOnly after explicit enrolment — see 1.5The single best cross-channel key; also the most legally loaded
PSP card fingerprintStripe fingerprint, Adyen recurring reference, Nexi/Worldline tokenMedium — PSP-scoped, breaks if you change PSPHighOnly after enrolmentCheaper fallback where PAR is not surfaced
Network token / DPANwalletMediumHigh but per-deviceOnly after enrolmentSame card on iPhone + Watch = 2 DPANs → PAR is what unifies them
Wifi captive portal MACAPEffectively dead — iOS 14+/Android 10+ randomise per-SSIDLowNo (and MAC is personal data under GDPR)Use the portal form (email/phone), not the MAC
Reservation recordour booking module, TheFork, Google ReserveMediumName+phoneYesOften the only identifier for full-service
Delivery-platform pseudonymGlovo, Wolt, Bolt Food, Uber Eats, Deliveroo, Pyszne/TakeawayLowPlatform-scoped hash or truncated nameMostly No — see 6.4Wolt/Glovo typically expose first name + masked phone; the platform is the controller
QR-order sessiontable QR → web appPer-session unless localStorage persistsLow aloneYes for the session (contract)Becomes gold when the session ends in a payment
Device push tokenour appMedium1 deviceYesRotates; needs re-registration handling
Wallet pass serialApple PassKit / Google WalletHigh1:1 with loyalty IDYesBest retention primitive we have (see 2.4)
Name + birthdaymanualLowLowYesBirthday is a marketing goldmine and a Art. 9 risk only if it implies something special-category (it doesn't)

#1.2 Graph model, not a golden record

Store an identity graph, not a mutable "golden customer row":

guest_identity (id, kind, value_normalised, value_hash, first_seen, last_seen, source, tenant_id)
guest_edge     (id, identity_a, identity_b, rule_id, confidence, evidence_json, created_at,
                created_by, superseded_by, unmerged_at, unmerge_reason)
guest_cluster  (id, tenant_id, primary_identity, cluster_version, computed_at)
guest_profile  (guest_id -> cluster_id, display fields, consent snapshot ref)

Rules that follow from this shape and that most vendors get wrong:

  • Merges must be reversible. A destructive merge of two guests is unrecoverable and the DSAR/erasure story becomes impossible ("delete me" from a wrongly-merged pair deletes a stranger's history). Merge = adding an edge; cluster membership is derived (union-find over edges above threshold) and recomputable.
  • Cluster recompute must be deterministic and versioned, so a campaign audience is reproducible for audit ("who received this and why").
  • Never inherit consent across a merge. Consent is attached to (identity, channel, purpose), not to the cluster. If phone A consented to SMS and email B did not consent to email, the merged guest is SMS-only. This is the #1 compliance bug in loyalty CDPs.

#1.3 Matching rules and confidence scoring

Deterministic tiers (auto-merge):

RuleScoreAuto-merge?
Same verified phone (OTP-confirmed)1.00Yes
Same verified email (double opt-in click)1.00Yes
Same loyalty ID scanned1.00Yes
Same PAR and both sides card-enrolled0.97Yes
Same PSP fingerprint and both card-enrolled0.92Yes
Same unverified phone typed at POS twice0.85Yes, but flag
App account login1.00Yes

Probabilistic tiers (never auto-merge for marketing; may soft-link for analytics only):

SignalWeight
Same normalised name (Jaro-Winkler ≥ 0.93)+0.25
Same delivery address (normalised, geocoded within 25 m)+0.30
Same birthday (day+month)+0.15
Overlapping visit at the same site within 5 min of a known check+0.10
Same device fingerprint on the QR ordering web app+0.20
Different country phone prefixes−0.20
Two active sessions at different sites within 10 min−0.60 (hard veto)

Policy: score ≥ 0.90 → merge; 0.65–0.90 → review queue in the back office with a one-click merge/split; < 0.65 → separate. Expect 1–4 % of a mature base to sit in the review queue; if it exceeds 8 % your normalisation is broken (usually phone country codes or gmail aliasing).

Cost of getting it wrong is asymmetric and worth stating to the customer: a false merge shows guest A guest B's allergy notes and spend history — a reportable personal-data breach. A false split just splits points. Bias hard toward false splits, and give staff a manual merge tool with an audit trail.

#1.4 Household grouping

Households are a marketing convenience, never a legal identity.

  • Signals: shared delivery address + different phones; same card PAR + different phones; repeated check-splitting between the same two guests; a "family" flag set explicitly in the app.
  • Use it for: suppression (don't SMS both partners the same offer twice), CLV roll-up, family/kids campaigns.
  • Do not use it for: showing one member another's transaction history, merging points balances without explicit action by both, or profiling one person from another's behaviour. A "family points pool" is a product feature requiring both parties to opt in, not an inference.

Card-based identification is the highest-value and highest-risk mechanism.

  1. Silent identification is unlawful in the EU. Fingerprinting a guest's card at the terminal and building a profile for a guest who never joined the programme has no workable lawful basis: not contract (no contract), not legitimate interest in practice (fails the balancing test — covert, unexpected, hard to object to), and Art. 14 transparency is unsatisfiable at a card terminal. [known]
  2. The compliant pattern is pre-registration. The guest explicitly enrols a card ("link the card you usually pay with"), we store hash(PAR) or hash(PSP fingerprint) against their account, and thereafter a matching hash on a payment identifies them. That is consent-based (GDPR Art. 6(1)(a)), transparent, and revocable. Enrolment can happen in-app, in a post-visit SMS link, or via a customer-facing display flow.
  3. PCI DSS scope. PAR itself is not account data and cannot be reverse-engineered to a PAN, and using it in place of PAN references can reduce PCI scope [verified]. But if you receive PAR by parsing an authorisation response inside your own app on the terminal, you are near the CDE and your assessor will care. Prefer PAR delivered by the PSP over a server-to-server webhook, out of band from the terminal.
  4. Contractual limits. Acquirer and PSP agreements frequently restrict use of tokens/PAR to payment purposes, and PAR availability is inconsistent — some CEE acquirers do not surface it at all. Budget for a fallback per PSP. [estimate — must be confirmed per acquirer contract]
  5. US: CPRA treats card-derived identifiers as personal information; a "financial incentive" notice is required if the loyalty discount is tied to that data (see 6.7).

Recommendation: ship card-linked identity in v1.1, not v1, gated behind explicit enrolment, with PAR-first and PSP-fingerprint fallback. Effort ~2.5 eng-months for two PSPs.


#2. Capture at the counter: the 4-second problem

The binding constraint is not software. It is that a cashier at a lunch rush will not spend more than ~3–5 seconds per transaction on anything that is not taking money, and will silently stop doing it within two weeks if it is not either (a) frictionless or (b) enforced by the order flow.

#2.1 Mechanism-by-mechanism, with honest capture rates

Capture rate = share of transactions that end with a usable, consented identifier. All figures [estimate] unless marked, drawn from patterns in retail/hospitality loyalty; treat as design inputs, not facts.

MechanismCashier timeGuest frictionRealistic capture (mature program)Data qualityVerdict
QR table ordering (guest orders + pays on own phone)0 sLow60–90 % of orders carry an identifier (payment or contact required anyway)HighBest lever that exists. Build this first
Order-ahead app / web0 sMedium (account)~100 % of that channel, but channel is only 3–15 % of volumeVery highBuild, but it does not solve the counter
Self-service kiosk prompt0 sLow25–45 % if prompted before payment; <10 % if afterHighStrong; requires kiosk hardware
Phone number typed at POS6–12 s first time, 2–4 s on repeat (prefix lookup)Medium15–35 % of transactions; 60–80 % of repeat guests once enrolledHighThe workhorse in QSR; kills throughput at peak
SMS e-receipt ("want the receipt by text?")4–8 sLow — feels like a service, not a pitch20–30 % give the numberHigh for phone, zero marketing consent (contract basis only)Excellent identity capture, weak consent capture — pair with a link in the SMS for opt-in
Card-linked auto-recognition (post-enrolment)0 sNoneNear 100 % of enrolled guests' card paymentsVery highThe endgame; needs the enrolment step to happen somewhere else first
Tap-to-join NFC tag on counter0 sLow but requires initiative3–8 % of transactionsHighCheap (EUR 0.30/tag); good in coffee shops
Receipt-footer QR0 sHigh (guest must act after leaving)1–4 % scan rate, of which ~50 % completeHighNearly worthless alone; useful as a backstop
Wifi captive portal0 sMedium10–25 % of guests connect; 40–70 % of those complete the form → 4–17 % of coversMedium (junk emails common; MAC randomisation prevents silent re-ID)Only worth it in cafés/hotels with long dwell; needs its own consent
Plastic loyalty card1–2 s scanCard must be carried30–60 % of enrolled members present itHighLegacy; cost EUR 0.10–0.40/card; only for older demographics
Wallet pass (Apple/Google)1–2 s scanLow after install55–80 % of members present it; passes are re-openable from lock screen near the venueHighBest retention mechanic per euro spent
Feedback QR on the check presenter0 sMedium3–10 %MediumDoubles as NPS capture (see §5)

#2.2 Capture math a founder can quote

Take a 3-site coffee chain, 350 transactions/site/day, 1,050/day total, ~31,500/month.

ScenarioMechanismsBlended captureNew identified guests/month (dedup, ~35 % new)Members after 12 mo
Counter-only, no QR orderingPOS phone (22 %) + receipt QR (2 %)~24 %~2,600~11,000–15,000
Counter + wallet pass + SMS receipt+ SMS receipt (25 %, overlapping)~35 %~3,300~16,000–20,000
+ QR ordering on 40 % of ordersQR at 70 % capture on that 40 %~52 %~4,300~22,000–28,000

The gap between row 1 and row 3 is the whole business case for owning the POS. Sell the POS as the CRM's capture device; do not sell the CRM as an add-on to someone else's POS — you will be capped at row 1.

#2.3 The consent/identity split (critical design decision)

Separate two things that every competitor conflates:

  • Identity capture (getting a phone number to send a receipt) → lawful basis: contract/legitimate interest, ~4 seconds, ~25 % capture.
  • Marketing consent (permission to send campaigns) → needs a real, evidenced, freely-given act, which cannot be done well at a busy counter.

Design: capture identity at the POS, then move the consent step to the guest's own device via the transactional SMS/email you just earned the right to send. "Here's your receipt. Tap to join Club — first coffee free." That link is where double opt-in, purpose granularity, and consent-text versioning happen — on a screen the guest controls, with a record we can defend. Expected join rate from a receipt SMS: 8–20 % [estimate].

#2.4 Wallet passes deserve their own line item

Apple PassKit + Google Wallet passes are underused by SMB restaurant CRMs and are cheap to build (~2 eng-months for both, including push updates via APNs pass updates and the Google Wallet REST API). Value: the pass updates its balance without an app; it can surface on the lock screen near the venue (relevance geofence, up to 10 locations per pass on iOS [known]); it survives phone changes; and it is the only "app-like" retention channel that does not require an app install. For SMB restaurants an actual native app is usually a waste — pass + web ordering beats a 2 %-install-rate app.


#3. Loyalty mechanics, and the offline constraint that defines the architecture

#3.1 Mechanic inventory

MechanicServer-authoritative?Offline-safe?ComplexityWhen to build
Points (earn X per EUR)YesEarn: trivially. Burn: needs floatMediumv1
Stamps ("buy 9 get 1")YesSameLowv1
Tiers (Silver/Gold)YesRead-only cached tier is fineMediumv1
Cashback (money-value wallet)YesBurn is real money — strict floatMediumv1.1
Vouchers/coupons (single-use)Issuance server-side, redemption must work offlineWith signed tokens (§3.3)Highv1
Promo/discount rules engineBoth — must evaluate identically on terminal and serverMandatory offlineVery highv1
Gift cards (stored value)YesOnly with a hard float capHigh + regulatedv1.2
Subscriptions ("coffee club", EUR 19/mo unlimited filter coffee")YesEntitlement check must work offlineMedium-Highv1.2 — high commercial value in CEE cafés
ReferralsYesAttribution can be asyncLowv1.2
Surprise-and-delight / random rewardsYesIssue online, redeem offlineLowv1.1

#3.2 Why offline is non-negotiable

A restaurant terminal loses connectivity: ISP outages, mobile-data dead zones in basements/courtyards, Wi-Fi contention at peak, router reboots by staff. Realistic per-terminal offline exposure: 5–90 minutes/month, p99 up to 4 hours [estimate]. During that window the POS must still take orders and take money — and if the loyalty layer fails open (everything free) or fails closed (nobody can redeem) you lose the account. Toast, Square and Lightspeed all degrade loyalty offline; incumbent loyalty specialists (Punchh, Paytronix, Como) are cloud-call-per-transaction and degrade worse because they sit behind a POS integration. This is the single most credible technical differentiator available to us.

#3.3 Signed offline-valid vouchers

Design:

Voucher token (CBOR/COSE_Sign1, Ed25519, ~140–200 bytes → QR or PDF417 on a wallet pass)
  vid    UUIDv7            voucher id (time-sortable → cheap expiry pruning)
  gid    guest id (opaque) 
  cid    campaign id
  val    {type: pct|abs|item|bogof, amount, currency, item_scope}
  scp    site/brand scope (bitmap or list)
  nbf    not-before  (epoch s)
  exp    not-after
  cnd    condition ref (rule bundle id + rule id)  // evaluated locally
  nonce  128-bit
  kid    signing key id

Terminal-side verification, in order:

  1. Signature valid against a pinned public key set (JWKS-style, kid rotation; keys pushed with the menu bundle; terminal refuses tokens signed by keys it has never seen).
  2. nbf/exp against a trusted clock: last successful server sync time + monotonic uptime. If now - last_sync > 72 h, refuse to redeem anything with an expiry inside the uncertainty window and warn the cashier. Staff do change terminal clocks.
  3. Local spent-set: RocksDB/SQLite table of redeemed vids, plus a Bloom filter of recently-spent ids gossiped over the site LAN (mDNS peer discovery, ~1 KB/min) so two terminals in the same room cannot both burn the same voucher while the site is offline.
  4. Condition evaluation via the portable rules evaluator (§3.6).
  5. Mark spent locally with an idempotency key, apply the discount before fiscalisation (§3.7), enqueue for upload.

Residual risk: cross-site double spend while both sites are offline. Bound it, don't try to eliminate it.

Expected annual loss ≈ vouchers_issued × P(redeemed) × P(both sites offline simultaneously) × P(guest attempts fraud) × face_value

With 500k vouchers/yr, 12 % redemption, 0.3 % simultaneous-offline, 2 % fraud attempt, EUR 4 face value → ~EUR 14/year. It is a rounding error. Say this out loud in the sales deck; competitors handwave it and lose deals to "what if the internet goes down".

#3.4 Balance float for points/cashback/gift cards

Points and money balances cannot be signed into a token because they change. Use an escrow/float model, not a CRDT counter (a naive PN-Counter permits unbounded overdraw and cannot express "spend at most what you have"):

  • Server grants each terminal (or site) a reservation: offline_allowance = min(balance, tier_cap) where tier_cap defaults to EUR 20 / 2,000 points, configurable per brand.
  • The reservation carries a lease (expires_at, typically 24–72 h) and a monotonically increasing lease_seq.
  • Reservations are refreshed on every online lookup of that guest — so the common case (guest visits their home site regularly, site is online 99.5 % of the time) always has a fresh float.
  • On reconnect, the terminal replays burn events with idempotency keys; server applies them to the append-only ledger.
  • Ledger of record: append-only loyalty_event(id, guest_id, type[earn|burn|adjust|expire|void], amount, currency_or_unit, source_terminal, check_id, idempotency_key, occurred_at, recorded_at, prev_hash). Balances are materialised views, never the source of truth. Hash-chaining prev_hash per guest gives cheap tamper evidence — useful for franchise disputes and for the "is my franchisee stealing points?" question.

Overdraw reconciliation policy (must be a per-brand setting, not a hardcoded behaviour):

Overdraw amountDefault action
≤ EUR 5Absorb, log, count as marketing expense
EUR 5–25Balance goes negative; next earn repays first; notify guest neutrally
> EUR 25Negative balance + block further offline redemption for that guest + ops alert
Repeated (≥3 events/90 d)Fraud queue; block offline redemption for that guest globally

Gift cards are different: never allow offline redemption above a hard cap (EUR 10–20) and never allow offline balance top-up. Gift-card fraud is organised and cheap.

#3.5 Regulatory landmines in stored value

  • E-money licensing (EU): closed-loop, single-brand gift cards fall under the "limited network" exclusion of Directive 2009/110/EC Art. 3(k) [known]. The moment you operate a shared wallet across unaffiliated restaurants (the network effect everyone wants), you risk being an e-money issuer or a payment institution. There are national notification thresholds for limited-network operators under PSD2 Art. 37(2) (commonly cited as EUR 1 m/12 months of transactions) [known — confirm per NCA]. Design implication: v1 wallets must be per-brand, not cross-brand.
  • US: CARD Act 2009 — gift-card funds cannot expire for 5 years [known]; state escheat/unclaimed-property law varies wildly (some states claim unredeemed balances). Do not ship US gift cards without counsel.
  • UA: stored value and "certificates" sit under NBU payment services rules; get local counsel before shipping gift cards in Ukraine. [estimate]

#3.6 The promo/discount rules engine (the most underestimated component)

Requirements that make this hard:

  1. Bit-identical evaluation on server (Go/TS/Kotlin) and terminal (Swift/Kotlin/TS). Two implementations will diverge; you will find out from a customer.
  2. Deterministic, bounded evaluation — no unbounded loops on a EUR 200 Android terminal during a lunch rush. Budget ≤ 15 ms per check recompute.
  3. Stacking is combinatorial. "Best deal for the guest" over N stackable promos is a knapsack-flavoured problem. Do not promise a global optimum. Ship: exclusivity groups + explicit priority + a bounded search over at most K=6 candidate combinations, and document that behaviour in the UI ("applied: 2 of 3 eligible offers").

Recommended shape: a declarative rule DSL authored in the back office, compiled server-side to a portable bytecode/AST (CEL-like or a restricted JSON-Logic) and shipped in a signed rule bundle alongside the menu. One evaluator implementation compiled to each platform (a Rust core with FFI/WASM bindings is the honest answer if you want one implementation; a hand-written interpreter in each language is faster to start and where the bugs live). Add a golden-test corpus of 500+ (cart, rules) → expected-discount fixtures run in CI against every platform build. Without that corpus this component will eat 2 extra engineer-months in production bugs.

Rule vocabulary v1: cart total thresholds, item/category quantity, time-of-day/day-of-week, site scope, channel (dine-in/takeaway/delivery), guest tier, first-visit, nth-visit, birthday window, voucher present, max uses per guest/per day/global, exclusivity group, "cheapest item free", "bundle price".

#3.7 The fiscal constraint that CEE founders discover too late

In Poland (kasy online), Ukraine (RRO/PRRO), Romania (AMEF), and other fiscalised markets, the discount must be present in the fiscal receipt at the moment it is issued. Consequences:

  • Loyalty must be applied before the check is fiscalised — so identity capture must happen at order time or at payment time, not after.
  • "Scan your receipt afterwards to claim points" works for earning but not for discounting.
  • Post-hoc goodwill credits must be issued as a new voucher for the next visit, never as a retroactive adjustment of a closed fiscal document.
  • Voids/refunds of loyalty-discounted checks need a matching loyalty reversal event tied to the fiscal correction document.

This is a hard architectural constraint. It also means "reward points redeemed" must map cleanly onto a discount line the fiscal printer accepts, with correct VAT treatment (a points redemption reducing the taxable base vs. a free item at 0 price have different VAT consequences — get a tax opinion per market; in several jurisdictions a "free" item is a supply requiring VAT on cost). [estimate — this is a genuine open legal question, flagged in §Open questions]


#4. Marketing: segmentation, automation, and the one feature that sells the product

#4.1 Segmentation and scoring

  • Segment engine: a query builder over a columnar store (ClickHouse recommended — 500 venues × 1,000 checks/day = ~180 M rows/yr, trivial for a single 16-core node). Segments are stored as ASTs, materialised on a schedule + evaluated live for small audiences. Support both "static list snapshot" (needed for audit/campaign reproducibility) and "live segment" (needed for automation entry).
  • RFM with venue-relative windows. A fixed "lapsed = 90 days" is wrong: a coffee shop's daily regular is lapsed at 10 days; a fine-dining guest is not lapsed at 120 days. Fit per-venue (and per-guest where n≥4) inter-visit interval distributions and define lapse as exceeding p80 of the guest's own interval, falling back to venue p80. This is a two-day statistics job that makes every winback flow 2–3× better.
  • CLV: BG/NBD + Gamma-Gamma (standard non-contractual BTYD models) are appropriate and cheap to fit. Be honest with customers: they are junk with <6 months of history or <500 repeat guests. For the first year, ship "trailing 12-month spend + predicted 90-day retention" and label it as such.
  • Useful derived attributes that competitors mostly lack because they don't own the POS: preferred daypart, preferred site, average party size, dine-in vs takeaway ratio, item affinity (top 5 SKUs), discount sensitivity (share of visits with a promo), price-tier drift, allergen/dietary inference from repeated item choices (careful: dietary choices can imply religion or health → Art. 9 special category. Do not build "halal/kosher/vegan" audiences from purchase inference. Only from explicitly self-declared preferences.)

#4.2 Channels: real costs

ChannelUnit costNotes
SMS Poland (Twilio)$0.0457 / segment [verified 2026-08]Alphanumeric sender ID free on Twilio but PL operators require sender registration via the aggregator; a Polish/UA local aggregator (SMSAPI, Turbo SMS) is typically 40–70 % cheaper at volume [estimate]
SMS Ukraine~$0.05–0.09 / segment via global CPaaS [estimate]; local aggregators materially cheaperCyrillic = 70-char segments — budget 2–3 segments per marketing SMS in UA/BG/RS
Email (Amazon SES / Brevo)~$0.0001–0.001 / message [known]Effectively free; deliverability is the cost
Push (FCM/APNs)FreeRequires an app (low install rate)
Wallet pass push updateFreeThe best free channel we have
WhatsApp (Meta Cloud API)Marketing ~$0.01–0.14/msg depending on market; utility/auth ~80–90 % cheaper [verified 2026-08]. Poland moved to its own, lower rate card 1 Jul 2026; Ukraine moves to a standalone, higher utility/auth rate card 1 Oct 2026; from 1 Oct 2026 utility/service messages inside the service window become chargeable [verified 2026-08]Template pre-approval, per-template; opt-in must be channel-specific
Viber Business MessagesEUR 100/month minimum commitment per account, EUR 150/month for Ukraine and several CEE markets [verified 2026-08]; per-message ~EUR 0.006–0.02 [estimate]Viber has real reach in UA/BG/GR; the monthly minimum makes it unviable per-restaurant → we must aggregate it as one sender across tenants, which raises a controller/branding question

Cost-per-incremental-visit sanity check (coffee chain, PL): 20,000-member base, SMS to the 6,000 lapsed at EUR 0.042 = EUR 252. Offer: free pastry (COGS EUR 0.55). If holdout-measured incremental visits = 3.5 % of treated (210 visits) at EUR 6.80 average check and 68 % gross margin → EUR 971 gross profit − EUR 252 send − EUR 116 pastry COGS = EUR 603 net, 2.4× ROI. That arithmetic, computed automatically and shown per campaign, is the product.

#4.3 Automation flows (ship these six, in this order)

FlowTriggerChannelTypical measured lift [estimate]
Welcome / first rewardJoinEmail + wallet pass25–40 % redeem the welcome offer; drives 2nd visit
Post-visit thank-you + feedbackCheck closed + 2 hSMS or email3–10 % feedback response; feeds §5
Lapsed winback (tiered by RFM)Guest-relative lapse thresholdSMS > email2–6 % incremental visit rate
BirthdayBirthday − 5 d, expires +14 dSMS + wallet15–30 % redemption; highest-margin flow
Lapsed VIP (top decile, gone)Top-10 % spender crosses lapseManual task to the manager, not an SMSSmall n, huge value; a phone call beats a coupon
Second-visit nudge1 visit only, +10 dSMSConverting 1→2 visits is the biggest CLV lever in the whole system

Guardrails that must be in the engine, not in a runbook: global frequency cap (default max 4 marketing messages/guest/month), quiet hours per locale (marketing SMS at 07:00 is a complaint generator; several jurisdictions restrict marketing calls/messages by hour — treat 21:00–09:00 local as forbidden by default), channel-priority fallback (push → wallet → email → SMS, cheapest capable channel first), and per-campaign spend caps in EUR.

#4.4 Closed-loop attribution: the mechanism (the killer feature)

Everyone claims "closed-loop attribution" and means "we counted coupon redemptions". Coupon counts overstate value massively — most redeemers would have visited anyway. The real mechanism, which is only possible because we own the POS:

  1. Randomised holdout at audience-build time. When a campaign audience is materialised, deterministically assign bucket = hash(guest_id || campaign_id) mod 100; buckets 0–9 = holdout (configurable 5–20 %). Stratify by RFM decile and home site so treatment and control are balanced.
  2. Freeze the audience (static snapshot rows: campaign_audience(campaign_id, guest_id, variant, bucket, rfm_decile, site_id, assigned_at)). Reproducible for audit and for DSAR ("why did I get this?").
  3. Attribution window, default 14 days (configurable; 7 for coffee, 30 for fine dining).
  4. Outcome = POS revenue, joined on guest_id from checks, not from clicks. Compute per-guest: visits, gross revenue, discount cost, gross margin (needs recipe costing — even a crude category-level margin table works).
  5. Incremental revenue = (mean_margin_treated − mean_margin_holdout) × n_treated. Report with a bootstrapped 95 % CI and the net number after channel cost and discount cost.
  6. Power check, shown in the UI before send. With a 6,000-person audience, 10 % holdout, typical per-guest spend variance in a coffee concept, you can detect roughly an 8–12 % relative lift [estimate — needs calibration on real variance]. Below ~1,500 treated guests, most campaigns are statistically undetectable. Say this in the product. "This campaign is too small to measure; we'll pool it with the next 3 sends into a rolling estimate" is a trust-building message, and no competitor ships it.
  7. Rolling program-level incrementality: maintain a permanent 3–5 % global holdout that receives no marketing, to measure the whole programme's lift, not just individual campaigns. This is what lets you answer "is your loyalty programme worth its subscription fee?" with a number — the single strongest renewal argument in the category.

Engineering note: attribution joins must be resilient to late-arriving offline checks (a check from an offline terminal may land 4 hours late). Recompute attribution on a rolling 48-hour lag; never report final numbers before window_end + 48 h.

#4.5 A/B testing

Deterministic bucketing on hash(guest_id || experiment_id), minimum 2 variants + holdout, sequential-testing guardrail (don't let users peek and stop early — either fix the sample size up front or implement an always-valid sequential test). Test copy, offer value, send time, and channel. The highest-value experiments are offer value (does EUR 2 off beat a free pastry?) and send time — both trivially automatable and both worth real money to the customer.


#5. Reservations, guest profiles, feedback, reviews

#5.1 Reservations / waitlist / table management

This is a big, separate product (SevenRooms, OpenTable, TheFork, Resy) and a full build is ~6 eng-months on its own. Components: floor-plan editor, shift/service definitions, pacing rules (covers-per-15-min caps), table-combination logic, walk-in waitlist with SMS "your table is ready", no-show tracking and deposits (Stripe/Adyen holds), channel ingestion (own widget, Google "Reserve with Google", TheFork).

Recommendation: do not build this for v1. It is only needed for full-service, it competes with entrenched marketplaces that also send inbound demand (which we cannot), and it will not close a deal on its own. Build a minimal waitlist + walk-in SMS (≈1.5 eng-months) which is what most casual venues actually want, and integrate rather than replace for real reservations. Revisit if the ICP shifts to full-service/fine dining.

#5.2 Guest profile at the point of service

Cheap and disproportionately loved by full-service operators: notes, allergies, preferences (favourite table, wine, "always sparkling water"), VIP flag, lifetime spend, last 5 visits, last 5 items, open complaints. Must surface on the POS/handheld within 1 tap of the check and in the reservation view.

Privacy note: allergy and dietary data is health data (GDPR Art. 9) when it describes a medical allergy. Lawful basis: explicit consent (Art. 9(2)(a)) captured from the guest, or vital-interest arguments that you should not rely on. Practical design: allergy notes are (a) explicitly entered by the guest or with their verbal agreement recorded by staff, (b) shown only to staff on shift at that venue, (c) excluded from analytics and from all marketing segmentation, (d) short-retained (e.g. 24 months from last visit), (e) never exported to an ESP. This must be enforced in the schema (separate table, separate access role, tagged special_category=true, blocked from segment builder), not in a policy PDF.

#5.3 Feedback / NPS and service recovery

  • Trigger a 1-question survey (NPS or 1–5 stars) 2 hours post-visit via the channel we already have. Expect 3–10 % response [estimate].
  • Route detractors to a human, not a page. Score ≤ 6 → immediate task to the site manager with the check, items, server, and time; SLA timer; a "make it right" action that issues a signed voucher (§3.3) redeemable offline on the next visit. This closed service-recovery loop is genuinely valuable and cheap (~1 eng-month once the voucher and task primitives exist).
  • Route promoters to a public review with a one-tap link. Do not gate: selectively soliciting reviews only from happy guests violates Google's and TripAdvisor's policies and, in the EU, the Unfair Commercial Practices Directive as amended by the Omnibus Directive (fake/selectively-solicited reviews) [known]. Solicit from everyone; just handle detractors privately first. This is a compliance point competitors get wrong and you can attack.

#5.4 Review management

  • Google Business Profile API: read reviews, reply to reviews, manage locations. Access requires being allowlisted for the Business Profile APIs — the approval process is slow (weeks) and non-trivial for a new vendor [known]. Start this application in month 1 of the project; it is a long-pole item with no engineering fix.
  • TripAdvisor: partner-gated content API, paid, restrictive; realistically out of reach for a small vendor in year 1 [known].
  • Yelp / Facebook: Yelp Fusion is restricted for review content; Facebook Page reviews via Graph API require permissions review [known].
  • Verdict: ship Google-only review inbox + AI-drafted replies (a legitimately good use of an LLM: draft a reply in brand voice, human approves). ~2 eng-months. Defer everything else.

#6.1 Who is the controller? (decide this before writing schema)

ModelControllerConsequence
A. Per-tenant silo — each restaurant's guest data is theirs aloneRestaurant = controller, we = processor (Art. 28 DPA)Legally simple, sells easily to chains, no network effect, guest must join each brand separately
B. Consumer network — one guest wallet across all venues on the platformWe = controller (or joint controller, Art. 26) of the consumer-facing identityPowerful (one QR everywhere, cross-brand acquisition), but we need our own consumer privacy notice, our own lawful basis, an Art. 26 arrangement with every tenant, and we become the DSAR respondent

Recommendation: build A, with the schema partitioned so B is addable. Concretely: guest rows are tenant-scoped; a nullable network_identity_id column can later link them when (and only when) the guest opts in to the network. Do not silently share guest data across tenants — several EU DPAs would treat that as an unlawful disclosure, and it is also a commercial betrayal that will surface in a chain's security review.

#6.2 Lawful basis, per processing purpose

PurposeLawful basisNotes / traps
Take an order, take payment, issue a receiptArt. 6(1)(b) contract
Keep the transaction for tax/fiscal purposesArt. 6(1)(c) legal obligationRetention set by tax law: DE 8–10 yrs (AO §147 / HGB §257), PL 5 yrs from year end, UA ~1,095 days, most EU 5–10 yrs [known]. This beats erasure (Art. 17(3)(b))
Loyalty account, points ledger, tier calculationArt. 6(1)(b) contract — the programme T&Cs are a contractNot consent. Getting this right means a guest cannot "withdraw consent" and demand their points survive
Personalised offers / profiling membersArt. 6(1)(a) consent in DE/FR/AT/NL practice; 6(1)(f) LI with opt-out arguable elsewhereDivergent DPA views. Ship consent-based; it is portable and defensible. LI requires a documented LIA
Email marketingePrivacy Dir. 2002/58/EC Art. 13 → consent, or soft opt-in (existing customer, own similar goods/services, opt-out offered at collection and in every message)DE: UWG §7(3) soft opt-in exists but its four conditions are strictly read; double opt-in is the de facto evidentiary standard in DE [verified]
SMS/MMS marketingSame Art. 13 → consentPoland's PKE (Prawo komunikacji elektronicznej), in force 10 Nov 2024, replaced the old Telecom Act art. 172 regime and tightened direct-marketing consent [verified]. Treat PL as consent-only
WhatsApp / Viber marketingConsent + platform policy (Meta requires a documented, channel-specific opt-in)Channel-granular consent is a schema requirement, not a nice-to-have
Wifi captive portalArt. 6(1)(a) consent + ePrivacy Art. 5(3) for terminal-equipment accessSeparate consent from the loyalty consent
Card-linked identificationArt. 6(1)(a) consent, at enrolment only§1.5
Allergies / dietary needsArt. 9(2)(a) explicit consent§5.2
Analytics on aggregate salesNot personal data once aggregatedKeep a pseudonymised path
Sending audiences to Meta/Google AdsConsent, and a documented joint-controller position (post-Fashion ID)Very common vendor shortcut; do not ship it in v1

Also: Art. 30 records of processing, Art. 35 DPIA — a loyalty programme doing systematic profiling of guests at scale is a plausible DPIA trigger; budget one DPIA template + per-market review (~EUR 3,000–8,000 external counsel per major market [estimate]).

GDPR Art. 7(4): consent is not freely given if performance of a contract is conditional on consent to processing not necessary for that contract.

The clean structure:

  1. Layer 1 — join the programme = a contract. You get points and member prices. Basis: Art. 6(1)(b). This is allowed to be conditional; the discount is the consideration for the contract, not for consent.
  2. Layer 2 — marketing consent = separate, optional, unbundled. Per channel (email / SMS / WhatsApp / push), per purpose (offers / newsletter / profiling). The member discount must not depend on ticking these. If it does, several EU DPAs (DE, NL, AT notably) will call the consent invalid, which retroactively invalidates every campaign you sent.
  3. Layer 3 — profiling. Optional, separately consented, clearly explained ("we look at what you buy to pick your offers").

A member who ticks nothing still gets points and still gets transactional messages (receipts, "your points expire in 14 days" is arguably transactional/contractual, but a DPA may see it as marketing — draft it neutrally and keep it factual, no offers inside).

Art. 22 (automated decisions with legal or similarly significant effects): tier-based pricing is probably not "similarly significant", but if you build dynamic per-guest pricing, it very plausibly is. Flag before building personalised prices.

#6.4 Delivery-platform data

Glovo, Wolt, Bolt Food, Uber Eats, Deliveroo are controllers of their customer data. What they pass to the restaurant (first name, masked phone, address for delivery) is provided for order fulfilment. Using it to build a marketing profile is (a) usually contractually prohibited, (b) hard to justify under GDPR without a notice the guest never saw. Design rule: delivery orders create a separate, marketing-suppressed guest record, linked into the cluster only for analytics (spend, frequency), never as a marketing target — unless the guest independently joins with the same phone. Build the suppression flag into the segment engine so it cannot be bypassed by a clever query.

Ranked by defensibility:

MethodEvidence qualitySpeedVerdict
Link in the transactional SMS/email → guest's own device, double opt-inBest — IP, UA, timestamp, exact text version, click proofAsyncPrimary mechanism
Guest enters details on our web/QR ordering flow with unbundled checkboxesVery goodAsyncPrimary mechanism
Customer-facing display (second screen) with tick + confirmGood if the wording is legible and untimed6–12 sUse where CFD exists (kiosk, counter with a customer screen)
Guest replies "JOIN" to a shortcode/numberGood (carrier-logged)AsyncGood in UA/PL; needs a keyword-capable number
Cashier ticks a box on the guest's behalfWorthless — this is not consent, and DPAs have fined for it1 sProhibit in the UI. Do not build the button
Paper form with a signatureGood but unusable at scale60 sOnly for a card-based legacy migration

Records of consent schema (this is what a regulator asks for, and what almost no SMB vendor can produce):

consent_record(
  id, tenant_id, guest_identity_id, purpose_key, channel,
  state ENUM(granted, withdrawn, expired, pending_confirm),
  policy_text_id,            -- FK to the exact version shown
  policy_text_sha256,        -- immutable hash of the wording
  locale, ui_surface,        -- 'sms_link' | 'qr_web' | 'cfd' | 'kiosk' | 'app'
  captured_at, confirmed_at, -- double opt-in confirm
  evidence JSONB,            -- ip, user_agent, terminal_id, staff_id, message_sid
  withdrawn_at, withdrawal_source,
  prev_record_id             -- full history chain, never overwrite
)
consent_policy_text(id, tenant_id, purpose_key, locale, body, sha256, effective_from)

Key rules: append-only (never UPDATE a consent row), store the hash of the exact wording shown, keep withdrawal proof as carefully as grant proof, and make every outbound message check consent at send time (not at audience-build time — a 4-hour-old audience can contain someone who unsubscribed 3 hours ago).

#6.6 DSAR, erasure, and the offline-device problem

This is the requirement that quietly dictates your sync architecture.

An erasure request must reach: primary DB, read replicas, the analytics warehouse, search indexes, the campaign/queue system, the ESP/SMS provider, the app's local caches, and every POS terminal that has a cached guest lookup, a cached float reservation, or a queued offline event. Terminals may be powered off for weeks.

Mechanism:

  1. Tombstones over the same sync channel as everything else. erasure_order(guest_id, tenant_id, issued_at, scope, deadline) is a first-class replicated object. Terminals apply it on receipt and acknowledge; the server tracks per-device ack.
  2. Device inventory + staleness SLA. Any device that has not synced in > 30 days is flagged; on next connect it must apply the pending tombstone log before it is allowed to serve loyalty lookups. Devices offline > 90 days are treated as lost: revoke their credentials, and their encrypted local store becomes unreadable (per-device data keys, revocable server-side — this is the only real answer to "the tablet is in a drawer and I can't erase it").
  3. Erase vs. pseudonymise. Transaction rows survive erasure (Art. 17(3)(b), tax law). Implementation: null the guest_id FK, replace with an unlinkable tombstone id, delete the identity rows and the profile. Keep an erasure_log(request_id, tenant, hashed_subject, issued_at, completed_at, systems_acked) for accountability without keeping the person.
  4. Suppression lists survive. You must keep a hashed record of "do not contact this address" — deleting it means the person gets marketed to again. Document this in the privacy notice; it is a legitimate-interest/legal-obligation retention.
  5. Backups. Standard, defensible position: backups are excluded from immediate erasure, purged by rotation within N days (make N ≤ 35), erasure is re-applied if a backup is ever restored. Write it in the DPA.
  6. Access/portability (Art. 15/20): generate a JSON + CSV export: profile, consents (with wording), loyalty ledger, transaction summary, campaign sends and why the guest was targeted (segment name + rule). Target 1-click, ≤ 30 days, realistically ≤ 24 h automated.
  7. Multi-tenant subtlety: if the same person is a member of 12 brands on our platform, an erasure request to Brand A must not touch Brands B–L. Erasure is tenant-scoped. Our own consumer-facing UI (if model B) must make that explicit.

Estimated effort for a credible DSAR/erasure implementation including offline propagation: 3 engineer-months. It is not optional for EU sales; enterprise chain procurement will ask for it in the security questionnaire.

#6.7 US: CCPA/CPRA

  • Loyalty programmes are explicitly addressed: you may offer a "financial incentive" (a discount for data) but must give a notice of financial incentive including a good-faith estimate of the value of the consumer's data and the method used to calculate it (Cal. Civ. Code §1798.125(b)) [known]. The California AG ran an enforcement sweep specifically on loyalty programmes in January 2022 [known]. Almost nobody complies with the valuation-methodology part; a compliant, templated version is a genuine selling point to US chains.
  • Uploading a customer list to Meta/Google for advertising is a "sale" or "share" under CPRA → requires a "Do Not Sell or Share My Personal Information" link and honoring Global Privacy Control signals [known]. Sephora's $1.2 M settlement (2022) turned on exactly this [known].
  • Plus state-by-state proliferation (VA, CO, CT, TX, OR, and more), each with slightly different opt-out and sensitive-data rules. Cost of US privacy compliance for a small EU vendor: EUR 25,000–60,000 in counsel + ~2 eng-months of product work [estimate]. Combined with TCPA exposure on SMS (47 U.S.C. §227, statutory damages $500–$1,500 per message, and a mature plaintiffs' bar) [known], the US is a bad first market for a 4-person team.

#6.8 Market compliance cost, ranked

MarketPrivacy/marketing compliance burdenEst. one-off legal + eng cost
UALowest. Law No. 2297-VI (2010) is thin; a GDPR-alignment bill has been pending in the Rada for years [known, status uncertain]. Marketing consent required under advertising/telecom rules but enforcement is lightEUR 5–12 k [estimate]
PL / CZ / ROMedium. GDPR + national ePrivacy (PL's PKE is strict on marketing consent). Active DPAs but proportionateEUR 15–30 k per country [estimate]
DE / AT / NLHigh. Double opt-in expected, DPAs active on loyalty/profiling, works councils for staff data, strong preference for consent over LIEUR 30–60 k [estimate]
FR / ES / ITHigh. CNIL is prescriptive on loyalty and on cookies/tracking; Italian Garante is aggressive on marketingEUR 30–60 k [estimate]
USHigh and different in kind: CPRA financial-incentive notices, state patchwork, TCPA litigation riskEUR 25–60 k + ongoing [estimate]

Note: the ePrivacy Regulation proposal was withdrawn by the Commission (2025 work programme) [known], so national fragmentation is permanent for the foreseeable future. Budget per-country marketing-consent logic as a permanent product feature, not a one-off.


#7. Build vs. buy for the plumbing

ComponentBuy candidatesReal costDecisionWhy
Identity graph / CDPSegment, RudderStack, HightouchSegment MTU pricing becomes brutal at scale (500 venues × 5k guests = 2.5 M MTU) [known]; RudderStack has a self-hostable OSS coreBUILDThis is the product. No CDP understands checks, covers, offline terminals, or per-tenant controllership
Event pipelineRudderStack OSS, Kafka/Redpanda, NATSSelf-hostBUY (OSS), self-hostRedpanda/NATS JetStream for the POS→cloud event bus; ~0.5 eng-month vs. months to build
Campaign orchestration / journeysBraze (EUR 60 k+/yr), Customer.io (per-profile), Klaviyo (per-contact)All priced per end-consumer profileBUILDFatal pricing mismatch: we would resell their per-profile cost across hundreds of tenants at our own flat per-venue price. Margin goes negative at scale. Also none of them can gate a send on POS-side consent state
Message transportTwilio, Infobip, Bird/MessageBird, Vonage, SMSAPI (PL), TurboSMS (UA), Brevo, Amazon SES, Postmark, FCM/APNs, Meta Cloud APIPer-message (§4.2)BUY, abstracted, ≥2 providers/channel/countryCost arbitrage (local aggregators are 40–70 % cheaper in PL/UA) + failover + regulatory sender registration differs per country. Abstraction ~1.5 eng-months, +0.5 per extra provider
Consent management (CMP)OneTrust, Cookiebot, UsercentricsEUR 100–1,000/mo, web-onlyBUILDNo CMP handles POS terminals, offline devices, or per-tenant loyalty T&C versioning. Buy only for our own marketing website
Wallet passesPassKit.com, Passcreator~EUR 50–500/moBUILDApple PassKit + Google Wallet REST are well-documented; ~2 eng-months and it removes a recurring cost and a dependency in the critical path
Analytics warehouseBigQuery, Snowflake, ClickHouse CloudClickHouse self-hosted ≈ EUR 200–600/mo for our volumes [estimate]BUY (ClickHouse), self-hostCheap, fast enough, GDPR-friendlier (EU-hosted, our control)
ReservationsSevenRooms, TheFork, OpenTableIntegrateBUY/INTEGRATE for v16 eng-months for a worse product with no demand-generation network
Review managementReputation.com, BirdeyeEUR 200–600/mo/locationBUILD (Google only)Only Google matters for the SMB segment; the rest are gated APIs
Email deliverability infraBUY (SES/Brevo) + own DKIM/SPF per-tenant subdomainPer-tenant sending domains are essential; one tenant's spam complaints must not poison the pool
BTYD / CLV modelsBUILD (standard models, ~150 lines)Not worth a vendor

What must be ours, in one line: the identity graph, the loyalty ledger, the offline redemption protocol, the rules engine, the consent record, and the incrementality/attribution engine. Everything else is rentable.


#8. Competitive reality

VendorPositioningPrice [source]Where they are weak
PAR PunchhEnterprise QSR loyalty, US, "40 %+ of top-100 US restaurant brands"Custom; category commonly EUR/USD 1,500–5,000+/mo per brand plus setup, small end from ~$500/mo [verified — vendor-adjacent sources, treat as indicative]Enterprise-only sales motion; US-centric; depends on a POS integration it doesn't own → offline behaviour is whatever the POS allows; slow, expensive implementations
PaytronixEnterprise loyalty + stored value + online ordering, USCustom, enterprise [known]Same as Punchh; heavy, US-only, no EU privacy story
SevenRoomsFull-service CRM + reservations, global~$300–500/loc/mo (small groups) to $500–1,000+/loc/mo enterprise [verified — indicative, custom-quoted]Reservation-led; loyalty ledger is thin; no POS ownership → no offline redemption, weak transaction-level data unless integrated
Como (Como Sense)Mid-market POS-integrated loyalty + app, IL/global incl. some CEECustom, no public pricing [verified — none published]Dated app UX; integration-dependent; per-brand app economics poor for single sites
PiggySelf-serve loyalty, NL/EU, hardware-agnostic$79 / $299 / $849 per month + enterprise [verified 2026-08]Shallow POS integration; generic retail, not restaurant-aware; no offline redemption guarantees; weak on covers/checks semantics
FlipdishOrdering-led (web/app/kiosk) with loyalty attached, IE/EUFrom EUR 49/mo (annual) / 69 monthly for ordering site; EUR 79/99 with app [verified 2026-08]Loyalty is a feature of ordering, not a CRM; no real segmentation/attribution; not a POS
Toast Marketing / LoyaltyBundled with Toast POS, USAdd-on modules, roughly $75–165/mo/location [known — verify]Only for Toast POS; US/CA/UK/IE only. Not available in CEE. Marketing tooling is basic (no incrementality)
Square LoyaltyBundled with Square, contact-tier priced~$45–105/mo/location by active contacts [known — verify]Basic points/stamps only; no journeys, no attribution, POS-locked
Lightspeed Advanced MarketingAdd-on to Lightspeed RestaurantAdd-on pricing [known]Thin, POS-locked
Syrve / iiko (iikoCard)The real CEE/UA incumbent. POS + loyalty + delivery, deep in UA/PL/CZ/BalticsModule pricing, typically EUR 30–120/mo/site depending on modules [estimate]Dated UX; on-prem/hybrid heritage; complex to configure; weak modern marketing automation and no incrementality measurement; strong offline behaviour though — do not assume they're weak there
Poster POSCloud POS with built-in loyalty, UA/PL/CZ~EUR 25–60/mo/site [estimate]Loyalty is basic (points/discounts); no journeys, no attribution, no wallet passes
UDSLoyalty-app network, UA/CIS/global~EUR 30–100/mo/site [estimate]Consumer-network model, thin POS integration, brand dilution (their app, not yours)
VoucherifyPromotion/rules engine as an APIUsage-priced, from ~$0–1,000+/mo [known]Not a competitor — a possible buy for the rules engine if we want to skip §3.6. But it is cloud-only, which breaks offline redemption. Dead end for us

The gap we can occupy: POS-owned, offline-correct loyalty with holdout-based revenue attribution, priced at SMB/small-chain levels (EUR 39–99/site/month for CRM on top of the POS), in CEE languages, with credible EU privacy tooling. Nobody currently sits at that intersection. The nearest threat is Syrve/iiko extending its marketing module, and Toast/Lightspeed entering CEE.


#9. Sequencing recommendation

PhaseContentsWhy this order
v1 (months 0–10)Identity graph + phone/email capture at POS, SMS e-receipt, wallet passes, points/stamps/tiers, signed offline vouchers + float, rules engine, segments, 6 automation flows, email+SMS transport, consent records + DSAR, holdout attributionThis is the smallest set that is demonstrably better than incumbents. Attribution is the demo that closes deals
v1.1 (months 10–14)Card-linked identity, cashback, feedback/NPS + service recovery, Google review inbox, referrals, A/BDeepens the moat; low regulatory cost
v1.2 (months 14–20)Gift cards, subscriptions/coffee club, WhatsApp + Viber, waitlist, household grouping, review AI repliesRevenue expansion; gift cards need legal work first
LaterFull reservations/table management, cross-brand consumer network (controller model B), US marketEach is a strategy change, not a feature

Honest constraint for S1 (bootstrapped, 3.5 effective engineers): 55 eng-months for CRM v1 alone = ~16 calendar months, on top of a POS build that is at least as large. S1 cannot deliver both in 24 months. The only S1-viable paths are (a) CRM-first on top of someone else's POS (accepting the ~24 % capture ceiling from §2.2 and a fragile integration dependency), or (b) a radically narrower POS (single-concept, e.g. coffee/QSR counter-service only, one payment provider, one fiscal market) so the combined scope fits. Say this to the owner plainly.


#Build effort

Engineer-months for the CRM/loyalty/marketing/privacy domain only. Assumes a competent full-stack team with AI-assisted development (which helps 25–35 % on CRUD/UI/adapters and **0–10 % on distributed-systems correctness, rules-engine parity, and legal design** — do not discount those rows).

#Work itemEng-monthsVariance drivers
1Guest identity graph: schema, normalisation, deterministic + probabilistic matching, merge/unmerge with audit, review queue, search4.03.0 if deterministic-only; 6.0 with a tuned probabilistic model + a labelled eval set
2Capture surfaces: POS phone lookup, SMS e-receipt, QR join landing, kiosk prompt, wifi portal hook4.0×1.5 if you support 3 POS form factors (tablet, Sunmi, desktop) with separate UIs
3Wallet passes (Apple PassKit + Google Wallet) incl. push updates, geofences, per-tenant branding2.0+1.0 for per-tenant certificate management at scale
4Loyalty ledger: append-only events, idempotency, hash chain, points/stamps/tiers/expiry, materialised balances3.5+1.5 for multi-currency and franchise-level ledger separation
5Offline redemption protocol: COSE/Ed25519 vouchers, key rotation, trusted clock, LAN spent-set gossip, float leases, reconciliation, fraud caps5.03.5 if you accept "vouchers only, no offline balance"; 7.0+ if you also do offline gift cards. Highest-risk item in the doc
6Promo/rules engine: DSL, server compiler, portable evaluator on 2–3 platforms, stacking/exclusivity, 500-case golden corpus in CI5.03.5 with a Rust/WASM single-implementation core; 7.0 with hand-written per-platform interpreters (and more prod bugs)
7Segmentation engine over ClickHouse: AST query builder, static + live segments, suppression rules2.5+1.0 for a genuinely good non-technical UI
8RFM / lapse modelling / BTYD CLV with per-venue calibration1.5+1.0 if you productise model explanations
9Campaign + journey engine: triggers, waits, branching, frequency caps, quiet hours, spend caps, send-time consent re-check4.5+1.5 for a visual flow builder vs. templated flows
10Channel abstraction + adapters (Twilio, one PL aggregator, one UA aggregator, SES/Brevo, FCM/APNs)3.0+0.5 per additional provider; +1.5 for WhatsApp Cloud API incl. template lifecycle; +1.0 for Viber
11A/B + holdout + incrementality reporting (bucketing, frozen audiences, 48 h lag recompute, CI reporting, power warnings, program-level holdout)2.5+1.0 for margin-accurate reporting (needs recipe/category costing)
12Consent & preference centre: versioned policy text, append-only consent records, per-channel/per-purpose, double opt-in, POS-safe capture flows3.0+1.0 per additional market with divergent rules; DE alone is +0.5
13DSAR / erasure / portability incl. offline-device tombstone propagation, per-device keys + revocation, ack tracking3.0Cannot be cut for EU sales. +1.0 if backups/warehouse are not designed for it from day one
14Retention & pseudonymisation jobs, subprocessor register, DPA tooling, Art. 30 records, audit log1.5
15Data pipeline: POS events → bus → ClickHouse, CDC, late-arriving offline checks, backfill, idempotency3.0+1.5 if multi-region EU/US data residency is required
16Guest profile at point of service: notes, allergies (Art. 9 isolation), VIP, history surfaced on POS/handheld1.5
17Feedback/NPS + detractor routing + service-recovery voucher issuance1.5
18Google Business Profile review inbox + AI-drafted replies2.0Gated on GBP API approval (calendar risk, not eng risk)
19Multi-tenant admin, RBAC, per-brand theming, staff audit log for CRM actions2.5+1.0 for franchise hierarchies (brand → region → franchisee → site)
20CRM reporting: cohorts, retention curves, campaign P&L, member-vs-non-member basket analysis2.5
21Legal/DPIA/T&C/consent copy per market (engineering time to implement counsel's output)1.5Plus EUR 15–60 k external counsel per market tier (§6.8)
v1 subtotal (items 1–17, 19–21; excludes reviews)~55Range 45–75
Deferred: reservations/table management6.0Recommend integrate, not build
Deferred: gift cards + e-money guardrails2.0Legal review first
Deferred: subscriptions / coffee club incl. offline entitlement2.5High commercial value in CEE cafés
Deferred: referrals1.0
Deferred: card-linked identity (PAR + 2 PSPs)2.5v1.1
Deferred: household grouping1.5
Full domain, everything above~73

Calendar translation: S1 (3.5 effective engineers) → ~16 months for v1 CRM alone. S2 (8 engineers on this domain) → ~7 months, and S2 can parallelise items 5, 6, 9 and 13 which are the critical path.


#Open questions / what would change this answer

  1. Do we own the POS, or not? If we ship CRM on someone else's POS, capture drops from ~52 % to ~24 % of transactions (§2.2), offline redemption becomes impossible to guarantee, and the entire differentiation collapses. This is the load-bearing assumption of the whole document. Would change: everything.
  2. Controller model A vs. B. A cross-brand consumer wallet is worth a great deal commercially (guest acquisition for tenants) and costs a whole extra legal posture plus consumer-facing brand. Deciding this after v1 ships is expensive; deciding the schema for it now is cheap. Resolve in month 1.
  3. VAT treatment of points redemptions and "free item" rewards in PL/UA/CZ/RO. I have flagged this as a genuine risk (§3.7) and cannot resolve it from training data. A wrong answer here means every fiscal receipt we print is wrong. Needs a tax opinion per market before the loyalty ledger design is frozen — EUR 3–6 k each [estimate].
  4. Does our target acquirer/PSP surface PAR in CEE? If PAR is unavailable, card-linked identity degrades to PSP-scoped fingerprints, which break on PSP change and don't unify Apple Pay with the physical card. Confirm with the chosen PSP before promising card-linked recognition.
  5. Is SMB single-site actually a viable CRM buyer? A 40-member/month café takes 18+ months to reach audiences where campaign measurement is meaningful (§4.4 power analysis). The CRM's value may only be real at 2–20 sites. If true, the ICP shifts to small chains, which changes pricing, sales motion, and the priority of franchise hierarchy features (item 19).
  6. Google Business Profile API access. Approval is slow and discretionary; if refused, review management is a manual-paste feature. Apply in month 1; it is a calendar risk with no engineering workaround.