Notifications composer (ops guide)
Questa è la pagina Compose notification del control plane:
https://control.extrinsiccognition.com/p2e/notifications
Da qui mandi messaggi che appaiono dentro il launcher Playroll (e in futuro anche email). Il sistema è già live: la pagina compone l'evento, lo invia, il launcher dei destinatari lo riceve entro 60 secondi (o subito se mettono fuoco sulla finestra).
Prima di iniziare leggi sempre la sezione "Verifica prima di inviare" più sotto — un invio è irreversibile e tocca utenti reali.
Chi può aprire la pagina
Accesso gated da Cloudflare Access + crm_user_roles su Supabase. Devi avere
il ruolo admin o cm. Se vedi un 403 al primo caricamento, scrivi a Marco
per essere aggiunto al ruolo. Le persone autorizzate al momento sono
Phil, Nico, Denise, Kame, Mattia, più gli admin EC.
Cosa sa già fare il sistema (live oggi)
- Componi una notifica con categoria + sottotipo + tier
- Scrivi copy in italiano e/o inglese
- Aggiungi una CTA opzionale (esterna o deeplink in-app)
- Targetizza i destinatari per gruppo, release track, lingua, data di creazione account, oppure passandone gli UUID specifici
- Vedi in tempo reale quanti utenti riceveranno il messaggio
- Conferma con friction (
INVIAda digitare) prima dell'invio - Vedi nella history (in fondo alla pagina) le ultime 50 notifiche inviate, con conteggio destinatari
Notifiche programmatiche vs craftate
Questa è la cosa più importante da avere chiara prima di aprire la pagina.
Le 8 categorie definite dalla spec si dividono in due famiglie:
5 categorie programmatiche — non si emettono mai da questa pagina. Partono dai loro source system quando le condizioni scattano:
| Categoria | Source system |
|---|---|
payment_readiness | Trigger nel Contributor Portal su cambio iban_status / documents |
validation_outcome | Pipeline validazione upload |
payout_lifecycle | Backend payouts + webhook Wise |
app_update | Release pipeline (playroll-installer, CI Vincenzo) |
annual_cap | Backend cumulative tracker (P3) |
3 categorie craftate — sono quelle che vedi nel dropdown della pagina e che puoi inviare tu manualmente:
| Categoria | Quando si usa |
|---|---|
cohort_announcement | Comunicazioni ops (manutenzione, novità Discord, ringraziamenti) |
catalog | Aggiunta/rimozione gioco dal catalogo Playroll |
system | Escape hatch per casi non coperti dalle altre due |
Se hai bisogno di emettere ad-hoc una programmatica (es. backfill di un
evento che era andato perso), non farlo dalla UI: fallo via SQL diretto o
chiedi a Marco di scrivere uno script una-tantum. Esponendo le 5
programmatiche nel dropdown si rischierebbe troppo facilmente di mandare un
payout_sent finto.
Cosa non è ancora live, anche se la UI lo lascia presagire
Sii consapevole di queste limitazioni prima di lanciare:
| Cosa | Stato |
|---|---|
Filtro per language (it/en) | Inerte oggi. La colonna playroll_users.language è NULL per tutti gli utenti finché non finiamo EC-216. Se selezioni it o en qui, il count torna 0. |
Filtro per library_contains (giochi posseduti) | Live (EC-203). Cerca un gioco nella sezione "Audience builder" → autocomplete giochi catalogo (min 2 caratteri). Solo giochi con mapping Steam sono filtrabili; il count destinatari diventa solo gli utenti che hanno almeno uno dei giochi selezionati nella loro libreria Steam. |
| Email come canale | Mai inviata oggi. Per i tier T0 / T1-money/legal il sistema scrive una riga in notification_deliveries con status='queued', ma nessun worker li raccoglie ancora. In backlog (EC-194). |
| Throttle automatico su cohort_announcement | Live (EC-214). Max 1 critical + 2 standard per finestra rolling 7 giorni. Lato UI vedi un chip critical: X/1 · standard: Y/2 nel pannello "1. Event type" quando selezioni cohort_announcement. Quando sei al limite il bottone Send si disabilita e compare la data della prossima finestra disponibile. Il backend rifiuta comunque con HTTP 429 cohort_throttle_exceeded se qualcuno tenta di bypassare la UI. |
| CRUD gruppi via UI | Live. I gruppi founders, ops, beta si popolano dalla pagina Players (vedi runbook People & Roles). |
| Modal bloccante T0 nel launcher | La spec PM lo prevede ma il launcher al momento mostra T0 come tutte le altre nella tab Notifiche, senza modal. Marco aprirà un ticket quando definiamo dove vive visivamente. |
| Drafts (salva bozza) | Non implementato. Se navighi via dalla pagina perdi tutto. |
I 4 tier di urgenza
| Tier | Rendering nel launcher | Quando usarli |
|---|---|---|
| T0 | Modal bloccante (non ancora implementato — appare come T1 oggi) | Solo per cose che richiedono azione immediata (T&C, payout bloccato). Richiedono CTA obbligatoria. |
| T1 | Banner interruttivo + tab + badge, 12s auto-dismiss | Pagamento imminente, traguardo €100/€400, payout fallito, annuncio critico |
| T2 | Banner ambient (solo se launcher in foreground) + tab + badge, 6s | Default per la maggior parte degli annunci |
| T3 | Solo badge + entry nella tab, niente banner | Cose informative che possono aspettare (es. update disponibile) |
Il tier di default viene proposto in base al sottotipo. Cambialo solo se sai quello che fai.
Audience builder: come selezionare i destinatari
Due modalità:
- Everyone → tutti i 124 utenti registrati. Usa solo per cose che servono davvero a tutti (es. T&C re-acceptance). Se sbagli a digitare una T1/T2 e selezioni Everyone, 124 persone ricevono un'idiozia.
- Custom filter → combinazione AND di:
- Groups: clic sui gruppi seedati (
founders,ops,beta). Per aggiungere/togliere membri vai sulla pagina Players (runbook People & Roles). - App track:
stable,test,dev. Selezionandotestmandi solo agli 8 utenti attualmente sul test track.stable→ 0 oggi (nessuno ancora). - Language:
it/en. Inerte oggi (vedi sopra). Usa altri filtri. - Created after: filtra per data di registrazione.
- User IDs: incolla uno o più UUID, uno per riga. Modalità chirurgica — usala per mandare a una persona sola (test, ack mirato).
- Library contains (Steam): cerca un gioco (es. "Elden Ring") e
aggiungilo come pill. Selezionandone più di uno il filtro è OR (utente
deve avere ≥1 dei giochi). Solo giochi con mapping Steam sono filtrabili
(gli altri non compaiono nell'autocomplete). Tipico per
catalog.new_game_added— manda solo a chi già possiede quel gioco.
- Groups: clic sui gruppi seedati (
Il conteggio destinatari si aggiorna entro mezzo secondo dopo ogni
modifica. Se vedi 0 recipients il bottone Send è disabilitato.
Verifica prima di inviare
Prima di cliccare Send leggi questa checklist:
- Categoria + sottotipo + tier sono coerenti. Es. non usare
cohort_announcementcon tier T0 — il T0 esiste per modal blocking, non per gli annunci comunità. - La copy italiana c'è e non ha errori. Per money/legal (
payment_readiness,payout_lifecycle,annual_cap) chiedi review a Guido prima. - Il count destinatari è quello che ti aspetti. Se mandi a
foundersma il gruppo è vuoto vedrai 0 — niente arriverà. Devi prima popolare il gruppo (sotto). - CTA target: se hai messo un URL esterno, aprilo a parte e verifica
che funzioni. Se è un deeplink, sappi che oggi non c'è un router deeplink
nel launcher (è in arrivo); rendi il CTA
external_urlonone. - Throttle cohort: enforced automaticamente. Se sei al limite il chip diventa rosso e Send si disabilita — non serve più contare a mano. La data della prossima finestra disponibile è mostrata accanto al chip.
Solo dopo questi 5 check clicca Send. Confermi digitando INVIA nel modal.
Cosa succede dopo l'invio
- Il server inserisce 1 riga in
notification_event(l'evento canonico) - Inserisce N righe in
notification_state, una per ogni destinatario (sono queste che il launcher legge) - Per i tier che hanno email come canale (T0, T1-money/legal), inserisce
anche N righe in
notification_deliveriesconstatus='queued'. Le email non vengono ancora inviate (EC-194); le righe restano nel queue. - La pagina rimanda alla history view che ora mostra il tuo evento in cima.
- Sui launcher dei destinatari:
- Entro 60 secondi (polling timer) la notifica appare nella tab Notifiche.
- Oppure immediatamente se l'utente clicca sulla finestra del launcher (trigger on focus).
- Se ha abilitato il sound, sente un chime al primo arrivo (solo T1/T2).
L'evento e le righe state restano nel DB per sempre (oggi non c'è retention job). Il client mark-read è server-side e persiste tra reinstall.
Popolare un gruppo
Vai a /p2e/people/players, cerca l'utente per username/discord, clic sulla
riga → modal di edit → tick il gruppo (founders, ops, beta) → Save.
Il count nel composer si aggiorna entro mezzo secondo dalla prossima ricarica.
Per i dettagli (creare nuovi gruppi, bulk add, regole RBAC) vedi il runbook People & Roles.
Esempi pratici
Esempio 1: annuncio manutenzione a tutti
Category: cohort_announcement
Subtype: critical
Tier: T1
Title IT: Manutenzione domenica
Body IT: Domenica 18 maggio 8:00-10:00 la pipeline di validazione
sarà ferma per manutenzione. Le sessioni registrate in
quella finestra non perderanno l'audio ma saranno
validate al riavvio.
Audience: Everyone
CTA: Nessuna
Esempio 2: ringraziamento ai founders (sul test track)
Category: cohort_announcement
Subtype: standard
Tier: T2
Title IT: Grazie per i bug report
Body IT: Le segnalazioni di questa settimana hanno permesso di
sistemare 4 bug critici. Continua a usare F12 in app
per aprire il pannello bug.
Audience: Custom filter → Group: founders, App track: test
CTA: Nessuna
Esempio 3: messaggio mirato a un singolo utente (test ack)
Category: system
Subtype: custom
Tier: T2
Title IT: Verifica sound notification
Body IT: Se vedi questa notifica e senti il chime, il sistema
sound funziona.
Audience: Custom filter → User IDs: <singolo-uuid>
CTA: Nessuna
Cosa fare se qualcosa non torna
- "0 recipients" ma sono certo che il filtro è giusto: probabilmente stai
filtrando per
language(vedi sopra: NULL su tutti) o per un gruppo vuoto. - L'utente non vede la notifica nel launcher: chiedi che riapra il launcher o ci clicchi sopra. Polling timer 60s, on-focus immediato.
- Vedo un errore 500 nel network tab dopo Send: controlla la console del browser per il messaggio, e poi scrivi a Marco col contenuto della risposta. Il backend logga sempre il fallimento.
- Voglio "annullare" una notifica appena inviata: non c'è UI per farlo. Si
può cancellare via SQL (
delete from notification_event where id='<id>'fa cascade sulle state rows), ma se l'utente ha già fatto focus sul launcher l'avrà già vista. Misura sette volte, taglia una.
Riferimenti
- Spec sistema notifiche (playroll-cpp)
- Pagina composer (codice) (network-crm)
- Backend endpoint (sezione NOTIFICATIONS in routes.ts)
- Linear project — Notification System