#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:
| Identifier | Source | Stability | Uniqueness | Legally usable as a match key? | Notes |
|---|---|---|---|---|---|
| Phone (E.164) | POS ask, SMS receipt, reservation, delivery, app | High | ~1 person, but shared in couples/families | Yes (contract or consent) | Primary key candidate. Normalise with libphonenumber, store E.164 + country of origin |
| Email (normalised) | app, web order, wifi, receipt | High | ~1 person; gmail dot/plus aliasing | Yes | Normalise: lowercase, strip +tag, strip dots for gmail.com only |
| Internal loyalty ID | our issuance | Absolute | 1:1 | Yes | Barcode/QR on wallet pass; never reuse |
| Card PAR (EMVCo Payment Account Reference, 29-char alnum) | acquirer/PSP in auth response | High — survives re-issue in many cases, links PAN + Apple Pay DPAN + card-on-file token to one funding account [verified] | Very high | Only after explicit enrolment — see 1.5 | The single best cross-channel key; also the most legally loaded |
| PSP card fingerprint | Stripe fingerprint, Adyen recurring reference, Nexi/Worldline token | Medium — PSP-scoped, breaks if you change PSP | High | Only after enrolment | Cheaper fallback where PAR is not surfaced |
| Network token / DPAN | wallet | Medium | High but per-device | Only after enrolment | Same card on iPhone + Watch = 2 DPANs → PAR is what unifies them |
| Wifi captive portal MAC | AP | Effectively dead — iOS 14+/Android 10+ randomise per-SSID | Low | No (and MAC is personal data under GDPR) | Use the portal form (email/phone), not the MAC |
| Reservation record | our booking module, TheFork, Google Reserve | Medium | Name+phone | Yes | Often the only identifier for full-service |
| Delivery-platform pseudonym | Glovo, Wolt, Bolt Food, Uber Eats, Deliveroo, Pyszne/Takeaway | Low | Platform-scoped hash or truncated name | Mostly No — see 6.4 | Wolt/Glovo typically expose first name + masked phone; the platform is the controller |
| QR-order session | table QR → web app | Per-session unless localStorage persists | Low alone | Yes for the session (contract) | Becomes gold when the session ends in a payment |
| Device push token | our app | Medium | 1 device | Yes | Rotates; needs re-registration handling |
| Wallet pass serial | Apple PassKit / Google Wallet | High | 1:1 with loyalty ID | Yes | Best retention primitive we have (see 2.4) |
| Name + birthday | manual | Low | Low | Yes | Birthday 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):
| Rule | Score | Auto-merge? |
|---|---|---|
| Same verified phone (OTP-confirmed) | 1.00 | Yes |
| Same verified email (double opt-in click) | 1.00 | Yes |
| Same loyalty ID scanned | 1.00 | Yes |
| Same PAR and both sides card-enrolled | 0.97 | Yes |
| Same PSP fingerprint and both card-enrolled | 0.92 | Yes |
| Same unverified phone typed at POS twice | 0.85 | Yes, but flag |
| App account login | 1.00 | Yes |
Probabilistic tiers (never auto-merge for marketing; may soft-link for analytics only):
| Signal | Weight |
|---|---|
| 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.
#1.5 The legal limits of card-token matching (read this twice)
Card-based identification is the highest-value and highest-risk mechanism.
- 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]
- The compliant pattern is pre-registration. The guest explicitly enrols a card ("link the card you usually pay with"), we store
hash(PAR)orhash(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. - 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.
- 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]
- 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.
| Mechanism | Cashier time | Guest friction | Realistic capture (mature program) | Data quality | Verdict |
|---|---|---|---|---|---|
| QR table ordering (guest orders + pays on own phone) | 0 s | Low | 60–90 % of orders carry an identifier (payment or contact required anyway) | High | Best lever that exists. Build this first |
| Order-ahead app / web | 0 s | Medium (account) | ~100 % of that channel, but channel is only 3–15 % of volume | Very high | Build, but it does not solve the counter |
| Self-service kiosk prompt | 0 s | Low | 25–45 % if prompted before payment; <10 % if after | High | Strong; requires kiosk hardware |
| Phone number typed at POS | 6–12 s first time, 2–4 s on repeat (prefix lookup) | Medium | 15–35 % of transactions; 60–80 % of repeat guests once enrolled | High | The workhorse in QSR; kills throughput at peak |
| SMS e-receipt ("want the receipt by text?") | 4–8 s | Low — feels like a service, not a pitch | 20–30 % give the number | High 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 s | None | Near 100 % of enrolled guests' card payments | Very high | The endgame; needs the enrolment step to happen somewhere else first |
| Tap-to-join NFC tag on counter | 0 s | Low but requires initiative | 3–8 % of transactions | High | Cheap (EUR 0.30/tag); good in coffee shops |
| Receipt-footer QR | 0 s | High (guest must act after leaving) | 1–4 % scan rate, of which ~50 % complete | High | Nearly worthless alone; useful as a backstop |
| Wifi captive portal | 0 s | Medium | 10–25 % of guests connect; 40–70 % of those complete the form → 4–17 % of covers | Medium (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 card | 1–2 s scan | Card must be carried | 30–60 % of enrolled members present it | High | Legacy; cost EUR 0.10–0.40/card; only for older demographics |
| Wallet pass (Apple/Google) | 1–2 s scan | Low after install | 55–80 % of members present it; passes are re-openable from lock screen near the venue | High | Best retention mechanic per euro spent |
| Feedback QR on the check presenter | 0 s | Medium | 3–10 % | Medium | Doubles 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.
| Scenario | Mechanisms | Blended capture | New identified guests/month (dedup, ~35 % new) | Members after 12 mo |
|---|---|---|---|---|
| Counter-only, no QR ordering | POS 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 orders | QR 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
#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
| Mechanic | Server-authoritative? | Offline-safe? | Complexity | When to build |
|---|---|---|---|---|
| Points (earn X per EUR) | Yes | Earn: trivially. Burn: needs float | Medium | v1 |
| Stamps ("buy 9 get 1") | Yes | Same | Low | v1 |
| Tiers (Silver/Gold) | Yes | Read-only cached tier is fine | Medium | v1 |
| Cashback (money-value wallet) | Yes | Burn is real money — strict float | Medium | v1.1 |
| Vouchers/coupons (single-use) | Issuance server-side, redemption must work offline | With signed tokens (§3.3) | High | v1 |
| Promo/discount rules engine | Both — must evaluate identically on terminal and server | Mandatory offline | Very high | v1 |
| Gift cards (stored value) | Yes | Only with a hard float cap | High + regulated | v1.2 |
| Subscriptions ("coffee club", EUR 19/mo unlimited filter coffee") | Yes | Entitlement check must work offline | Medium-High | v1.2 — high commercial value in CEE cafés |
| Referrals | Yes | Attribution can be async | Low | v1.2 |
| Surprise-and-delight / random rewards | Yes | Issue online, redeem offline | Low | v1.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 idTerminal-side verification, in order:
- Signature valid against a pinned public key set (JWKS-style,
kidrotation; keys pushed with the menu bundle; terminal refuses tokens signed by keys it has never seen). nbf/expagainst a trusted clock: last successful server sync time + monotonic uptime. Ifnow - last_sync > 72 h, refuse to redeem anything with an expiry inside the uncertainty window and warn the cashier. Staff do change terminal clocks.- 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. - Condition evaluation via the portable rules evaluator (§3.6).
- 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)wheretier_capdefaults to EUR 20 / 2,000 points, configurable per brand. - The reservation carries a lease (
expires_at, typically 24–72 h) and a monotonically increasinglease_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-chainingprev_hashper 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 amount | Default action |
|---|---|
| ≤ EUR 5 | Absorb, log, count as marketing expense |
| EUR 5–25 | Balance goes negative; next earn repays first; notify guest neutrally |
| > EUR 25 | Negative 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:
- Bit-identical evaluation on server (Go/TS/Kotlin) and terminal (Swift/Kotlin/TS). Two implementations will diverge; you will find out from a customer.
- Deterministic, bounded evaluation — no unbounded loops on a EUR 200 Android terminal during a lunch rush. Budget ≤ 15 ms per check recompute.
- 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
| Channel | Unit cost | Notes |
|---|---|---|
| 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 cheaper | Cyrillic = 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) | Free | Requires an app (low install rate) |
| Wallet pass push update | Free | The 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 Messages | EUR 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)
| Flow | Trigger | Channel | Typical measured lift [estimate] |
|---|---|---|---|
| Welcome / first reward | Join | Email + wallet pass | 25–40 % redeem the welcome offer; drives 2nd visit |
| Post-visit thank-you + feedback | Check closed + 2 h | SMS or email | 3–10 % feedback response; feeds §5 |
| Lapsed winback (tiered by RFM) | Guest-relative lapse threshold | SMS > email | 2–6 % incremental visit rate |
| Birthday | Birthday − 5 d, expires +14 d | SMS + wallet | 15–30 % redemption; highest-margin flow |
| Lapsed VIP (top decile, gone) | Top-10 % spender crosses lapse | Manual task to the manager, not an SMS | Small n, huge value; a phone call beats a coupon |
| Second-visit nudge | 1 visit only, +10 d | SMS | Converting 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:
- 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. - 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?"). - Attribution window, default 14 days (configurable; 7 for coffee, 30 for fine dining).
- Outcome = POS revenue, joined on
guest_idfrom 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). - 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.
- 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.
- 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. Privacy and consent — the part that is genuinely hard
#6.1 Who is the controller? (decide this before writing schema)
| Model | Controller | Consequence |
|---|---|---|
| A. Per-tenant silo — each restaurant's guest data is theirs alone | Restaurant = 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 platform | We = controller (or joint controller, Art. 26) of the consumer-facing identity | Powerful (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
| Purpose | Lawful basis | Notes / traps |
|---|---|---|
| Take an order, take payment, issue a receipt | Art. 6(1)(b) contract | — |
| Keep the transaction for tax/fiscal purposes | Art. 6(1)(c) legal obligation | Retention 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 calculation | Art. 6(1)(b) contract — the programme T&Cs are a contract | Not consent. Getting this right means a guest cannot "withdraw consent" and demand their points survive |
| Personalised offers / profiling members | Art. 6(1)(a) consent in DE/FR/AT/NL practice; 6(1)(f) LI with opt-out arguable elsewhere | Divergent DPA views. Ship consent-based; it is portable and defensible. LI requires a documented LIA |
| Email marketing | ePrivacy 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 marketing | Same Art. 13 → consent | Poland'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 marketing | Consent + platform policy (Meta requires a documented, channel-specific opt-in) | Channel-granular consent is a schema requirement, not a nice-to-have |
| Wifi captive portal | Art. 6(1)(a) consent + ePrivacy Art. 5(3) for terminal-equipment access | Separate consent from the loyalty consent |
| Card-linked identification | Art. 6(1)(a) consent, at enrolment only | §1.5 |
| Allergies / dietary needs | Art. 9(2)(a) explicit consent | §5.2 |
| Analytics on aggregate sales | Not personal data once aggregated | Keep a pseudonymised path |
| Sending audiences to Meta/Google Ads | Consent, 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]).
#6.3 The "loyalty discount vs. freely given consent" problem
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:
- 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.
- 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.
- 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.
#6.5 Capturing consent at a POS terminal (hard, and mostly solved by not doing it there)
Ranked by defensibility:
| Method | Evidence quality | Speed | Verdict |
|---|---|---|---|
| Link in the transactional SMS/email → guest's own device, double opt-in | Best — IP, UA, timestamp, exact text version, click proof | Async | Primary mechanism |
| Guest enters details on our web/QR ordering flow with unbundled checkboxes | Very good | Async | Primary mechanism |
| Customer-facing display (second screen) with tick + confirm | Good if the wording is legible and untimed | 6–12 s | Use where CFD exists (kiosk, counter with a customer screen) |
| Guest replies "JOIN" to a shortcode/number | Good (carrier-logged) | Async | Good in UA/PL; needs a keyword-capable number |
| Cashier ticks a box on the guest's behalf | Worthless — this is not consent, and DPAs have fined for it | 1 s | Prohibit in the UI. Do not build the button |
| Paper form with a signature | Good but unusable at scale | 60 s | Only 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:
- 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. - 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").
- Erase vs. pseudonymise. Transaction rows survive erasure (Art. 17(3)(b), tax law). Implementation: null the
guest_idFK, replace with an unlinkable tombstone id, delete the identity rows and the profile. Keep anerasure_log(request_id, tenant, hashed_subject, issued_at, completed_at, systems_acked)for accountability without keeping the person. - 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.
- 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.
- 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.
- 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
| Market | Privacy/marketing compliance burden | Est. one-off legal + eng cost |
|---|---|---|
| UA | Lowest. 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 light | EUR 5–12 k [estimate] |
| PL / CZ / RO | Medium. GDPR + national ePrivacy (PL's PKE is strict on marketing consent). Active DPAs but proportionate | EUR 15–30 k per country [estimate] |
| DE / AT / NL | High. Double opt-in expected, DPAs active on loyalty/profiling, works councils for staff data, strong preference for consent over LI | EUR 30–60 k [estimate] |
| FR / ES / IT | High. CNIL is prescriptive on loyalty and on cookies/tracking; Italian Garante is aggressive on marketing | EUR 30–60 k [estimate] |
| US | High and different in kind: CPRA financial-incentive notices, state patchwork, TCPA litigation risk | EUR 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
| Component | Buy candidates | Real cost | Decision | Why |
|---|---|---|---|---|
| Identity graph / CDP | Segment, RudderStack, Hightouch | Segment MTU pricing becomes brutal at scale (500 venues × 5k guests = 2.5 M MTU) [known]; RudderStack has a self-hostable OSS core | BUILD | This is the product. No CDP understands checks, covers, offline terminals, or per-tenant controllership |
| Event pipeline | RudderStack OSS, Kafka/Redpanda, NATS | Self-host | BUY (OSS), self-host | Redpanda/NATS JetStream for the POS→cloud event bus; ~0.5 eng-month vs. months to build |
| Campaign orchestration / journeys | Braze (EUR 60 k+/yr), Customer.io (per-profile), Klaviyo (per-contact) | All priced per end-consumer profile | BUILD | Fatal 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 transport | Twilio, Infobip, Bird/MessageBird, Vonage, SMSAPI (PL), TurboSMS (UA), Brevo, Amazon SES, Postmark, FCM/APNs, Meta Cloud API | Per-message (§4.2) | BUY, abstracted, ≥2 providers/channel/country | Cost 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, Usercentrics | EUR 100–1,000/mo, web-only | BUILD | No CMP handles POS terminals, offline devices, or per-tenant loyalty T&C versioning. Buy only for our own marketing website |
| Wallet passes | PassKit.com, Passcreator | ~EUR 50–500/mo | BUILD | Apple PassKit + Google Wallet REST are well-documented; ~2 eng-months and it removes a recurring cost and a dependency in the critical path |
| Analytics warehouse | BigQuery, Snowflake, ClickHouse Cloud | ClickHouse self-hosted ≈ EUR 200–600/mo for our volumes [estimate] | BUY (ClickHouse), self-host | Cheap, fast enough, GDPR-friendlier (EU-hosted, our control) |
| Reservations | SevenRooms, TheFork, OpenTable | Integrate | BUY/INTEGRATE for v1 | 6 eng-months for a worse product with no demand-generation network |
| Review management | Reputation.com, Birdeye | EUR 200–600/mo/location | BUILD (Google only) | Only Google matters for the SMB segment; the rest are gated APIs |
| Email deliverability infra | — | — | BUY (SES/Brevo) + own DKIM/SPF per-tenant subdomain | Per-tenant sending domains are essential; one tenant's spam complaints must not poison the pool |
| BTYD / CLV models | — | — | BUILD (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
| Vendor | Positioning | Price [source] | Where they are weak |
|---|---|---|---|
| PAR Punchh | Enterprise 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 |
| Paytronix | Enterprise loyalty + stored value + online ordering, US | Custom, enterprise [known] | Same as Punchh; heavy, US-only, no EU privacy story |
| SevenRooms | Full-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 CEE | Custom, no public pricing [verified — none published] | Dated app UX; integration-dependent; per-brand app economics poor for single sites |
| Piggy | Self-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 |
| Flipdish | Ordering-led (web/app/kiosk) with loyalty attached, IE/EU | From 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 / Loyalty | Bundled with Toast POS, US | Add-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 Loyalty | Bundled 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 Marketing | Add-on to Lightspeed Restaurant | Add-on pricing [known] | Thin, POS-locked |
| Syrve / iiko (iikoCard) | The real CEE/UA incumbent. POS + loyalty + delivery, deep in UA/PL/CZ/Baltics | Module 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 POS | Cloud 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 |
| UDS | Loyalty-app network, UA/CIS/global | ~EUR 30–100/mo/site [estimate] | Consumer-network model, thin POS integration, brand dilution (their app, not yours) |
| Voucherify | Promotion/rules engine as an API | Usage-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
| Phase | Contents | Why 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 attribution | This 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/B | Deepens the moat; low regulatory cost |
| v1.2 (months 14–20) | Gift cards, subscriptions/coffee club, WhatsApp + Viber, waitlist, household grouping, review AI replies | Revenue expansion; gift cards need legal work first |
| Later | Full reservations/table management, cross-brand consumer network (controller model B), US market | Each 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 item | Eng-months | Variance drivers |
|---|---|---|---|
| 1 | Guest identity graph: schema, normalisation, deterministic + probabilistic matching, merge/unmerge with audit, review queue, search | 4.0 | 3.0 if deterministic-only; 6.0 with a tuned probabilistic model + a labelled eval set |
| 2 | Capture surfaces: POS phone lookup, SMS e-receipt, QR join landing, kiosk prompt, wifi portal hook | 4.0 | ×1.5 if you support 3 POS form factors (tablet, Sunmi, desktop) with separate UIs |
| 3 | Wallet passes (Apple PassKit + Google Wallet) incl. push updates, geofences, per-tenant branding | 2.0 | +1.0 for per-tenant certificate management at scale |
| 4 | Loyalty ledger: append-only events, idempotency, hash chain, points/stamps/tiers/expiry, materialised balances | 3.5 | +1.5 for multi-currency and franchise-level ledger separation |
| 5 | Offline redemption protocol: COSE/Ed25519 vouchers, key rotation, trusted clock, LAN spent-set gossip, float leases, reconciliation, fraud caps | 5.0 | 3.5 if you accept "vouchers only, no offline balance"; 7.0+ if you also do offline gift cards. Highest-risk item in the doc |
| 6 | Promo/rules engine: DSL, server compiler, portable evaluator on 2–3 platforms, stacking/exclusivity, 500-case golden corpus in CI | 5.0 | 3.5 with a Rust/WASM single-implementation core; 7.0 with hand-written per-platform interpreters (and more prod bugs) |
| 7 | Segmentation engine over ClickHouse: AST query builder, static + live segments, suppression rules | 2.5 | +1.0 for a genuinely good non-technical UI |
| 8 | RFM / lapse modelling / BTYD CLV with per-venue calibration | 1.5 | +1.0 if you productise model explanations |
| 9 | Campaign + journey engine: triggers, waits, branching, frequency caps, quiet hours, spend caps, send-time consent re-check | 4.5 | +1.5 for a visual flow builder vs. templated flows |
| 10 | Channel 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 |
| 11 | A/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) |
| 12 | Consent & preference centre: versioned policy text, append-only consent records, per-channel/per-purpose, double opt-in, POS-safe capture flows | 3.0 | +1.0 per additional market with divergent rules; DE alone is +0.5 |
| 13 | DSAR / erasure / portability incl. offline-device tombstone propagation, per-device keys + revocation, ack tracking | 3.0 | Cannot be cut for EU sales. +1.0 if backups/warehouse are not designed for it from day one |
| 14 | Retention & pseudonymisation jobs, subprocessor register, DPA tooling, Art. 30 records, audit log | 1.5 | — |
| 15 | Data pipeline: POS events → bus → ClickHouse, CDC, late-arriving offline checks, backfill, idempotency | 3.0 | +1.5 if multi-region EU/US data residency is required |
| 16 | Guest profile at point of service: notes, allergies (Art. 9 isolation), VIP, history surfaced on POS/handheld | 1.5 | — |
| 17 | Feedback/NPS + detractor routing + service-recovery voucher issuance | 1.5 | — |
| 18 | Google Business Profile review inbox + AI-drafted replies | 2.0 | Gated on GBP API approval (calendar risk, not eng risk) |
| 19 | Multi-tenant admin, RBAC, per-brand theming, staff audit log for CRM actions | 2.5 | +1.0 for franchise hierarchies (brand → region → franchisee → site) |
| 20 | CRM reporting: cohorts, retention curves, campaign P&L, member-vs-non-member basket analysis | 2.5 | — |
| 21 | Legal/DPIA/T&C/consent copy per market (engineering time to implement counsel's output) | 1.5 | Plus EUR 15–60 k external counsel per market tier (§6.8) |
| v1 subtotal (items 1–17, 19–21; excludes reviews) | ~55 | Range 45–75 | |
| Deferred: reservations/table management | 6.0 | Recommend integrate, not build | |
| Deferred: gift cards + e-money guardrails | 2.0 | Legal review first | |
| Deferred: subscriptions / coffee club incl. offline entitlement | 2.5 | High commercial value in CEE cafés | |
| Deferred: referrals | 1.0 | ||
| Deferred: card-linked identity (PAR + 2 PSPs) | 2.5 | v1.1 | |
| Deferred: household grouping | 1.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
- 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.
- 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.
- 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].
- 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.
- 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).
- 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.