docs(loyalty): go-live readiness snapshot — 2026-05-10
Some checks failed
CI / ruff (push) Successful in 16s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled

Captures where the loyalty pre-launch checklist actually stands after
tonight's prod readiness pass:

  - Step 1 (email templates seeded) 
  - Step 2 (Google Wallet config)  validated via wallet-debug
  - Step 3 (migrations)  all module heads incl. loyalty_011 on prod
  - Step 7 (Wallet real-device test) 
  - Steps 4, 5 (FR/DE/LB analytics keys, store-owner template
    permission) deferred — cosmetic / non-blocking
  - Step 6 (8 user-journey E2E tests) is the remaining human gate
  - Step 9 (Google Wallet production access) post-launch

Also records the SMTP path-change diagnosis (own mail server on port
465 blocked outbound from Hetzner; switched to 587 STARTTLS via
/admin/settings DB overrides) and the cosmetic fix shipped in
f2d1bdcd so the test email reports the *effective* config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 22:00:43 +02:00
parent f2d1bdcd49
commit d3b1670623
2 changed files with 152 additions and 14 deletions

View File

@@ -325,10 +325,13 @@ Tracked separately, not blocking launch.
Everything below must be completed before going live. Items are ordered by dependency. Everything below must be completed before going live. Items are ordered by dependency.
### Step 1: Seed email templates on prod DB > **Latest status snapshot:** see [Go-Live Readiness (2026-05-10)](../../proposals/loyalty-go-live-readiness.md)
- [ ] SSH into prod server > — only Steps 6 and 8 still pending; Step 9 is post-launch.
- [ ] Run: `python scripts/seed/seed_email_templates_loyalty.py`
- [ ] Verify: 20 rows created (5 templates × 4 locales) ### Step 1: Seed email templates on prod DB ✅
- [x] SSH into prod server
- [x] Run: `python scripts/seed/seed_email_templates_loyalty.py`
- [x] Verify: 20 rows created (5 templates × 4 locales)
- [ ] Review EN email copy — adjust subject lines/body if needed via admin UI at `/admin/email-templates` - [ ] Review EN email copy — adjust subject lines/body if needed via admin UI at `/admin/email-templates`
### Step 2: Google Wallet — already deployed, verify config ### Step 2: Google Wallet — already deployed, verify config
@@ -339,12 +342,13 @@ The Google Wallet integration is already deployed on the Hetzner server (see Ste
- Service account linked to Issuer with Admin role ✅ - Service account linked to Issuer with Admin role ✅
Verify after deploy: Verify after deploy:
- [ ] Restart app — confirm no startup error (config validator checks file exists) - [x] Restart app — confirm no startup error (config validator checks file exists)
- [ ] `GET /api/v1/admin/loyalty/wallet-status` returns `google_configured: true` - [x] `GET /api/v1/admin/loyalty/wallet-status` returns `google_configured: true`
- [x] Wallet diagnostics screen at `/admin/loyalty/wallet-debug` → Config Validation → all green (validated 2026-05-10)
### Step 3: Apply database migrations ### Step 3: Apply database migrations
- [ ] Run: `alembic upgrade heads` - [x] Run: `alembic upgrade heads`
- [ ] Verify migrations applied: `loyalty_003` through `loyalty_006`, `customers_003` - [x] Verify migrations applied: all four module heads current incl. `loyalty_011`
### Step 4: FR/DE/LB translations for new analytics i18n keys ### Step 4: FR/DE/LB translations for new analytics i18n keys
- [ ] Add translations for 7 keys in `app/modules/loyalty/locales/{fr,de,lb}.json`: - [ ] Add translations for 7 keys in `app/modules/loyalty/locales/{fr,de,lb}.json`:
@@ -375,12 +379,12 @@ Follow the **Pre-Launch E2E Test Checklist** at the bottom of `user-journeys.md`
- [ ] **Test 7:** Admin oversight (programs, merchants, analytics) - [ ] **Test 7:** Admin oversight (programs, merchants, analytics)
- [ ] **Test 8:** Cross-location disabled behavior (separate cards per store) - [ ] **Test 8:** Cross-location disabled behavior (separate cards per store)
### Step 7: Google Wallet real-device test (demo mode) ### Step 7: Google Wallet real-device test (demo mode)
Google Wallet currently works in **demo/test mode** — only your Google account and explicitly added test accounts can see passes. This is sufficient for launch testing. Google Wallet currently works in **demo/test mode** — only your Google account and explicitly added test accounts can see passes. This is sufficient for launch testing.
- [ ] Enroll a test customer on prod - [x] Enroll a test customer on prod
- [ ] Tap "Add to Google Wallet" on success page - [x] Tap "Add to Google Wallet" on success page
- [ ] Open Google Wallet on Android device — verify pass renders with merchant branding - [x] Open Google Wallet on Android device — verify pass renders with merchant branding
- [ ] Trigger a stamp/points transaction — verify pass auto-updates within 60s - [x] Trigger a stamp/points transaction — verify pass auto-updates within 60s
### Step 8: Go live ### Step 8: Go live
- [ ] Remove any test data from prod DB (test customers, test cards) - [ ] Remove any test data from prod DB (test customers, test cards)

View File

@@ -0,0 +1,134 @@
# Loyalty Go-Live Readiness — 2026-05-10
Snapshot of where the loyalty platform stands the night of 2026-05-10. The
canonical sequenced plan is still
[`app/modules/loyalty/docs/production-launch-plan.md`](../modules/loyalty/production-launch-plan.md);
this doc records the *current state* (✅ / ⏳ / 🟡) and what surfaced
during the prod readiness pass.
## TL;DR
The technical pre-launch checklist is green. The remaining gate is a
human one — walking the 8 user-journey E2E tests on prod with a real
test customer and confirming nothing surprises us. After that, flip the
loyalty platform live for FASHIONHUB's stores and start the Google
Wallet production-access review in parallel (13 day Google review,
non-blocking).
## Status board
| # | Pre-launch step | State | Notes |
|---|---|---|---|
| 1 | Seed loyalty email templates on prod | ✅ | 20 rows (5 templates × 4 locales) all `is_active=true` |
| 2 | Google Wallet config on Hetzner | ✅ | Wallet config validator green: credentials valid, issuer `3388000000023089598`, origin `https://rewardflow.lu`, default logo reachable |
| 3 | Database migrations | ✅ | All four module heads current incl. `loyalty_011` (acting-device audit) on prod |
| 4 | FR/DE/LB translations for analytics i18n keys | 🟡 | 8 keys still EN-only. Cosmetic, doesn't block soft launch |
| 5 | `messaging.manage_templates` permission for store owners | 🟡 | Only matters if merchants self-edit templates. Admin can edit centrally. Defer |
| 6 | 8 user-journey E2E tests | ⏳ | **The remaining gate** — user does this with a real test customer |
| 7 | Google Wallet real-device pass test | ✅ | Already confirmed earlier — cards register, points/redeem visible on personal Google Wallet |
| 8 | Go live | ⏳ | Gated by #6. Cleanup test data + enable platform feature flags for FASHIONHUB |
| 9 | Google Wallet production access | ⏳ | Post-launch, 13 day Google review. App-side change is zero; same issuer + service account, passes become public-visible once approved |
## What got sorted tonight
### SMTP wired to a self-hosted mail server
Started here:
- prod `.env` had `EMAIL_PROVIDER=sendgrid` + a SendGrid API key
- SendGrid free trial (60 days) had expired
- `SMTP_*` env vars were placeholders pointing at `smtp.example.com`
Discovered that `/admin/settings` lets you store SMTP config in the DB
(table `admin_settings`, category `email`) and those values **win over
.env**. User had already configured:
- `email_provider=smtp`
- `smtp_host=mail1.myservices.hosting`
- `smtp_port=465` ← problematic
- `smtp_user=support@wizard.lu` / encrypted password
- `smtp_use_ssl=true, smtp_use_tls=false`
Diagnosis from the prod container:
| Check | Result |
|---|---|
| DNS resolves `mail1.myservices.hosting` | ✅ `185.26.107.245` |
| TCP `mail1.myservices.hosting:465` | ❌ timed out |
| TCP `mail1.myservices.hosting:587` | ✅ open |
Either Hetzner blocks 465 outbound for this VPS or the provider
firewalls Hetzner's IP range on 465. Either way, port 587 (submission
+ STARTTLS) is the modern path and works.
**Fix:** changed `/admin/settings` to port 587, SSL off, TLS on. Test
email landed in inbox immediately, sender header `Support Wizard
<support@wizard.lu>` — proving the DB override was being used.
### Cosmetic bug found and fixed
The test email's body claimed the configuration that **would have been
used if .env were authoritative** — i.e. it said `Provider: sendgrid`
and `From: noreply@wizard.lu` even though the actual send went via
SMTP from `support@wizard.lu`. Two places in the code:
1. `app/modules/core/routes/api/admin_settings.py::send_test_email`
body template hardcoded `app_settings.email_provider` and
`app_settings.email_from_address`
2. `app/modules/messaging/services/email_service.py` — the "template
not found" `EmailLog` branch recorded `settings.email_provider` /
`settings.email_from_address` instead of the effective config
Both now read from `get_effective_email_config(db)` /
`self._platform_config`, so the test email page and audit logs reflect
what was actually used.
Commit: `f2d1bdcd` on master, deployed via Gitea Actions.
## What the user does next
In priority order:
1. **Tonight or tomorrow — review email copy.** Open
`/admin/email-templates` and skim the 5 loyalty templates (EN
locale). `loyalty_enrollment`, `loyalty_welcome_bonus` and
`loyalty_reward_ready` are the customer-visible ones — adjust
subject lines + body copy if anything reads off-brand.
2. **Walk the 8 user-journey E2E tests** — checklist at the bottom of
`app/modules/loyalty/docs/user-journeys.md`. Use a personal email
as the test customer.
3. **Flip live for FASHIONHUB** — clean any test data, double-check
Celery (`docker compose ps | grep celery`), enable loyalty feature
on FASHIONHUB's stores via the admin UI.
4. **In parallel, file Google Wallet production access**
[pay.google.com/business/console](https://pay.google.com/business/console)
→ Wallet API → Manage → Request production access. Use sample pass
screenshots from FASHIONHUB. Google reviews the Issuer, not
individual merchants — once approved all merchants on the platform
are covered.
## Open follow-ups (non-blocking)
These can wait but are worth tracking:
- **FR/DE/LB translations** for the 8 analytics i18n keys
(`store.analytics.revenue_title`, `store.analytics.cohort_title`,
etc.). EN shows through; cosmetic only.
- **`messaging.manage_templates` permission discovery for
merchant_owner role** — needed if/when merchants self-edit templates.
Admin can edit centrally for v1.
- **Failed-PIN-attempt reporting from Android tablet → server lockout
counter** — tablet bcrypts locally and silently fails; a stolen
tablet's brute-forcer doesn't trip server-side lockout. Add a tiny
`POST /pins/{id}/record-failed-attempt` endpoint plus a call from the
PinViewModel's failure branch.
- **Splash screen + per-action success animation** for the Android
tablet — Phase F polish that was intentionally deferred.
## Reference
- Canonical plan: [`app/modules/loyalty/docs/production-launch-plan.md`](../modules/loyalty/production-launch-plan.md)
- Hetzner runbook: [`deployment/hetzner-server-setup.md`](../deployment/hetzner-server-setup.md)
- Wallet diagnostics page: `/admin/loyalty/wallet-debug` (super admin only)
- Recent commits relevant to this session:
- `f2d1bdcd` fix(messaging): test email + EmailLog show effective config