m25: giocatori_destinatari_evento() include solo i giocatori attivi, quindi l'allenatore non riceve più i promemoria a 24 e 3 ore, per nessun tipo di evento (fino alla m24 li riceveva sempre, DD-034). Riceve ancora i messaggi dello staff. Test: nuovo destinatari-notifiche.test.ts (14 controlli sulle route vere con un servizio push finto: promemoria, messaggio dello staff, sondaggio, sollecito, turno palloni, permessi di allenatore e giocatore) e aggiornato permessi-allenatore. Documentazione: DD-039, tabella delle notifiche dell'allenatore, catalogo e tabella dei test che lo verificano. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
28 KiB
Modulo — Notifiche
Stato: implementato — un unico opt-in dispositivo abilita tutto il canale push, più un
centro notifiche in-app indipendente, con un pallino sull'avatar del profilo
File principali: src/lib/notifiche-smart.ts, src/lib/notifiche-utente.ts,
src/lib/push-client.ts, src/lib/webpush.server.ts, src/routes/api/public/push-config.ts,
src/routes/api/public/push-subscribe.ts, public/push-sw.js,
src/components/crapp/ui-bits.tsx (LinkProfilo, PallinoNotifiche)
Obiettivo
Tenere aggiornati i giocatori senza che debbano aprire l'app, con due meccanismi indipendenti:
- Push VAPID — arrivano anche ad app chiusa (messaggi dello staff, turno palloni, sollecito presenze, sondaggio pre-partita: elenco completo nel catalogo).
- Notifiche smart — notifiche locali mostrate solo ad app aperta, generate da badge, serie e obiettivi appena raggiunti; non è un canale push separato.
Gli avvisi fissi in Home (turno palloni, certificati in scadenza) non passano da nessuno dei due: sono card calcolate dai dati, che spariscono da sole quando la condizione finisce. In particolare il certificato in scadenza non genera push né voci nel centro notifiche (DD-035, specifica in profilo-giocatore.md).
Catalogo delle notifiche
Cosa fa partire una notifica, a chi arriva e su quali canali. Sono i canali indipendenti descritti
più sotto: push (solo ai dispositivi che hanno attivato «Notifiche»), centro notifiche
in-app (storico in notifiche_utente, per tutti i giocatori) ed email
(notifiche-email.md: ogni riga di notifiche_utente genera una mail, quindi
la colonna Email coincide con la colonna In-app).
| # | Notifica | Cosa la fa partire | Destinatari | Push | In-app | |
|---|---|---|---|---|---|---|
| 1 | Promemoria a 24 ore — «Promemoria: titolo», corpo «data alle ora» | Job pg_cron promemoria-eventi-24h, ogni ora, per gli eventi che iniziano entro le prossime 24 ore. Tipo evento_promemoria_24h. La push la manda il worker (M24). |
I convocati dell'evento, o tutta la rosa attiva se convocati è vuoto; mai gli allenatori (DD-039). |
sì (worker) | sì | sì |
| 2 | Promemoria a 3 ore — stesso testo | Job pg_cron promemoria-eventi-3h, ogni 15 minuti, per gli eventi che iniziano entro le prossime 3 ore. Tipo evento_promemoria_3h. La push la manda il worker (M24). |
Come il promemoria a 24 ore. | sì (worker) | sì | sì |
| 3 | Messaggio dello staff — titolo fisso «Messaggio dallo staff», corpo il testo scritto (max 300 caratteri) | Un admin, dalla tab «Notifiche» della dashboard: «Invia messaggio a tutti» o il pulsante della riga di un giocatore. Route notifica-personalizzata, tipo admin. |
Push: tutti i dispositivi iscritti, o quelli del giocatore scelto. In-app ed email: tutta la rosa attiva (allenatori compresi, anche senza dispositivi iscritti), o il giocatore scelto. | sì | sì | sì |
| 4 | Turno palloni — «Tocca a te prendere i palloni» / «Porta i palloni» | Un admin, con il pulsante nella pagina dell'evento. Solo eventi di tipo allenamento o partita. Route promemoria-palloni, tipo turno_palloni. |
L'incaricato del turno di quell'evento e, se è un'altra persona, chi aveva i palloni all'evento precedente. Se per l'evento non c'è un incaricato non parte nulla. | sì | sì | sì |
| 5 | Sollecito presenze — «Manca la tua risposta» | Un admin o un allenatore, con il pulsante nella pagina dell'evento. Route sollecita-presenze, tipo sollecita_presenze. |
I giocatori attivi (mai gli allenatori) che non hanno ancora risposto o hanno risposto «forse». | sì | sì | sì |
| 6 | Sondaggio pre-partita — «💩 Sondaggio pre-partita aperto» | Un admin, dalla pagina della partita. Route apri-sondaggio (vedi Scout Live). |
Push: tutti i dispositivi iscritti, tranne quelli degli allenatori. In-app ed email (tipo sondaggio_cacche): i giocatori attivi, allenatori esclusi, anche senza dispositivi iscritti. |
sì | sì | sì |
| 7 | Notifiche smart — badge appena sbloccato, serie a un traguardo, obiettivo di squadra tra il 90 e il 100%, badge social vinto | L'app, mentre è aperta: sono calcolate in locale da calcolaNotifiche(). |
Chi ha l'app aperta con uno slot giocatore in rosa selezionato; mai l'allenatore. Nessun invio verso altri dispositivi. | no (solo locale) | no | no |
Tutte le notifiche generate dal server (righe 1-6) hanno tutti e tre i canali (DD-038). La
differenza sta in come parte la push: quelle dei pulsanti (3-6) la mandano le route dell'app, quelle
dei promemoria (1 e 2) nascono nel database e la manda il worker mailer/ (vedi «Push dei
promemoria»). Le righe 4 e 5 riscrivono la notifica esistente se l'admin preme di nuovo il pulsante
per lo stesso evento, e lo stesso fa il sondaggio (vedi «Centro notifiche in-app»).
Cosa riceve l'allenatore, notifica per notifica: allenatore.md.
Cosa non genera nessuna notifica
- Creare, spostare, modificare o cancellare un evento: contano solo i promemoria prima dell'inizio (DD-030).
- L'arrivo di un risultato, di una pagella o di un voto MVP.
- Un giocatore che risponde a un evento: nessun avviso a chi gestisce gli eventi.
- Assenze e infortuni.
- La registrazione o il collegamento di un nuovo giocatore.
- Il certificato in scadenza: resta una card in Home (DD-035).
Comportamenti da conoscere
Sono l'effetto attuale della logica, non scelte documentate altrove:
- I promemoria (1 e 2) non distinguono il tipo di evento: scattano per allenamenti, partite ed eventi extra-campo (per esempio una cena). Il turno palloni (4), invece, esiste solo per allenamenti e partite.
- Un evento creato a poche ore dall'inizio riceve subito i promemoria che gli spettano: se mancano meno di 24 ore parte il promemoria «24 ore» al primo giro del job orario, e se ne mancano meno di 3 anche quello «3 ore», a distanza di pochi minuti.
- Ogni promemoria si genera una sola volta per giocatore, evento e tipo, anche se il giocatore
lo elimina (M23, DD-037): il job registra ciò che ha generato in
promemoria_eventi_generati, una tabella indipendente danotifiche_utente. Eliminare la notifica dal centro notifiche non la fa tornare e non accoda una nuova mail. Prima di M23 tornava: la riga eliminata veniva rigenerata al giro successivo del job, con una nuova mail a ogni giro fino all'inizio dell'evento. - Il registro non segue le modifiche all'evento: se un evento viene spostato dopo che il promemoria è partito, non ne parte uno nuovo per la data nuova. Chi viene aggiunto ai convocati dopo riceve invece il suo promemoria, perché il registro è per giocatore.
- Le notifiche 3, 4 e 5, una volta eliminate, non tornano da sole: riappaiono solo se un admin rimanda il messaggio o ripreme il pulsante.
- Un evento già iniziato non genera più promemoria.
- Quattro notifiche su sei dipendono da un gesto manuale (3, 4, 5 e 6): se nessuno preme il pulsante, nessuno viene avvisato. Gli unici automatismi sono i due promemoria.
- Per ogni evento i giocatori ricevono al massimo due mail automatiche (24 e 3 ore prima), più quelle dei pulsanti manuali.
- Non esistono preferenze per tipo di notifica: un solo interruttore per la push («Notifiche») e uno per le email («Email»). Il centro notifiche in-app non si può spegnere.
Test che verificano il catalogo
| Cosa | Test |
|---|---|
Promemoria 24 e 3 ore: destinatari (convocati, tutti i giocatori attivi se convocati è vuoto, mai l'allenatore), deduplica, eliminazione, coda push ed email |
test/integration/destinatari-notifiche.test.ts, promemoria-eventi.test.ts, push-promemoria.test.ts, notifiche-email.test.ts |
| Messaggio dello staff: push a tutti i dispositivi (allenatore compreso) o a uno solo, in-app a tutta la rosa attiva, riservato agli admin | destinatari-notifiche.test.ts, notifiche-utente.test.ts |
| Turno palloni: incaricato e chi li aveva prima, solo allenamenti e partite, riservato agli admin | destinatari-notifiche.test.ts, unit/palloni-core.test.ts |
| Sollecito presenze: chi non ha risposto o ha detto «forse», mai l'allenatore; permesso ad allenatore e admin, non ai giocatori | destinatari-notifiche.test.ts, unit/presenze.test.ts |
Sondaggio pre-partita: push senza allenatori, avviso in-app ed email ai giocatori attivi, upsert, riservato agli admin |
destinatari-notifiche.test.ts, sondaggio-notifiche.test.ts, unit/cacche.test.ts |
| Worker: esiti, backoff, tetto giornaliero, iscrizioni scadute | unit/mailer-core.test.ts |
| L'allenatore è fuori dalla rosa di gioco | unit/giocatori-squadra.test.ts (inRosa) |
Le push dei test vanno a un servizio push finto in locale: si verifica a chi arrivano, non la
consegna su un telefono. Non ha un test l'esclusione dell'allenatore dalle notifiche smart: la
catena useIo() → useRosa() → useNotificheSmart() è fatta di hook e si conosce dalla lettura del
codice; l'unico pezzo puro, inRosa, è coperto.
Dati
push_subscriptions (un dispositivo per riga, chiave endpoint, con le chiavi p256dh e
auth con cui si cifra il payload per quel dispositivo). La tabella promemoria_push non è
più usata da nessuno: serviva da coda del testo quando la push partiva vuota (DD-026).
Iscrizione alle notifiche push
In Profilo → Opzioni c’è un solo interruttore («Notifiche»). Non esistono preferenze separate per tipo di messaggio: l’iscrizione registra il dispositivo e lo rende destinatario di tutte le push (promemoria palloni, solleciti presenze) e abilita anche le notifiche smart in app, che usano lo stesso service worker.
- Il giocatore attiva «Notifiche» in
/profilo→ richiesta permesso browser. GET /api/public/push-configrestituisce solo la chiave pubblica VAPID.- Registrazione del service worker
public/push-sw.jsepushManager.subscribe(). POST /api/public/push-subscriberegistra endpoint e chiavi inpush_subscriptions(upsert).
All'avvio e quando l'app torna visibile viene richiesto l'aggiornamento della registrazione
push esistente con ServiceWorkerRegistration.update(). Non si chiede un nuovo permesso,
non si ricrea la sottoscrizione e non si cambia l'endpoint: anche chi ha già attivato le
notifiche deve ricevere le correzioni del worker senza spegnere e riaccendere l'interruttore.
Gli aggiornamenti contemporanei sono accorpati; un errore di rete non blocca l'app e si
riprova al ritorno in primo piano. Il worker attende skipWaiting() durante l'installazione.
Il browser controlla anche autonomamente gli aggiornamenti: questa richiesta esplicita
copre in particolare le sessioni lunghe della webapp (vedi il
ciclo di vita del service worker).
Ruolo delle tre route pubbliche
push-config— espone la sola chiave pubblica VAPID.push-subscribe— registra o rimuove l'iscrizione di un dispositivo.apri-sondaggio— premuto da un admin dalla pagina partita: manda a tutti i dispositivi iscritti (allenatori esclusi) l'avviso di apertura del sondaggio pre-partita e lo scrive anche innotifiche_utenteper i giocatori attivi, così compare nel centro notifiche e parte per email (DD-038; vedi Scout Live).
L'invio effettivo (src/lib/webpush.server.ts, funzione inviaPush) firma un JWT VAPID
(ECDSA P-256), cifra {title, body} per il dispositivo destinatario e fa una POST
all'endpoint push del browser; è riusato identico da sollecita-presenze.ts,
promemoria-palloni.ts e apri-sondaggio.ts.
Il testo viaggia dentro la push, cifrato in aes128gcm (RFC 8188/8291) con le chiavi del
dispositivo: il service worker fa event.data.json() e mostra la notifica senza toccare la
rete. È il punto decisivo per la consegna ad app chiusa — il browser sveglia il worker per
pochi secondi, e una fetch per recuperare il testo lo faceva morire prima di
showNotification (DD-026).
La POST porta Urgency: high. Con l'urgenza predefinita ("normal") un telefono in risparmio
energetico accumula i messaggi fino al risveglio: la notifica arriva solo quando il
dispositivo è già attivo — cioè, nella pratica, solo con l'app aperta.
Chi può farle partire (DD-024, DD-025)
Queste route usano la service role e saltano la RLS, quindi il permesso deve stare nella
route. Tutte e tre partono da un gesto di un amministratore dentro l'app, quindi il controllo
è uno solo (richiediAdmin in src/lib/auth-route.server.ts) e non serve configurare nessuna
variabile d'ambiente.
| Route | Controllo | Chi la chiama |
|---|---|---|
apri-sondaggio, sollecita-presenze, promemoria-palloni, notifiche-attive, notifica-personalizzata |
richiediAdmin — token della sessione Supabase, poi ruolo admin in user_roles |
l'app, da un pulsante o una vista riservati agli admin |
csi, push-config, push-subscribe |
nessuno | il browser prima del login, che una sessione non ce l'ha ancora |
notifiche-attive è a sola lettura: non manda push, restituisce gli id giocatore con almeno
un dispositivo iscritto in push_subscriptions (deduplicati). Alimenta la tab "Notifiche"
della dashboard admin (vedi Profilo giocatore), non l'invio effettivo.
La tab elenca tutti i giocatori attivi della squadra, non solo chi ha le notifiche abilitate:
l'icona (campana piena/barrata) distingue chi ha almeno un dispositivo iscritto da chi non
l'ha ancora attivata.
notifica-personalizzata manda un messaggio libero scritto dall'admin: senza giocatoreId
lo manda a tutti i dispositivi iscritti in push_subscriptions, con giocatoreId solo a
quelli di quel giocatore. Titolo fisso ("Messaggio dallo staff"), corpo il testo scritto
dall'admin (max 300 caratteri). Stessa logica di pulizia delle altre route: una sottoscrizione
che risponde 404/410 viene cancellata dalla tabella. Nella tab "Notifiche" della dashboard
admin c'è un bottone "Invia messaggio a tutti" sopra l'elenco e un bottone per riga giocatore.
Notifiche smart
calcolaNotifiche() (notifiche-smart.ts) genera un evento solo quando "c'è qualcosa di
reale": badge appena sbloccato, "sei a un passo" da un traguardo, serie che raggiunge un
traguardo esatto, obiettivo di squadra tra il 90 e il 100%, badge social vinto. Ogni notifica
ha un id deterministico; quelli già mostrati sono salvati in localStorage per non
ripetersi — deduplica puramente locale al dispositivo, non sincronizzata.
Chi le riceve. Solo chi ha selezionato sul dispositivo uno slot giocatore in rosa:
CelebrazioneBadge (montato una volta in __root.tsx) passa a useNotificheSmart() il giocatore
letto da useGiocatoreCorrente(), cioè useIo(), che lo cerca in useRosa(). Quell'elenco esclude
gli allenatori (inRosa, DD-034), che non hanno statistiche: per loro useIo() è null e
useNotificheSmart() esce subito, senza calcolare nulla né mostrare la card celebrativa o la
notifica del sistema. Vedi allenatore.md.
Centro notifiche in-app (M17)
Pallino sull'angolo dell'avatar del profilo in alto a destra (PallinoNotifiche dentro
LinkProfilo, src/components/crapp/ui-bits.tsx): rosso con il numero delle non lette; se
sono tutte lette resta neutro con il totale, così le lette restano raggiungibili per
eliminarle; senza notifiche non compare. Il tap sull'avatar porta sempre a /profilo, solo il
tap sul pallino apre il pannello (logica in pallinoNotifiche()). Fino a 0.9.2 era una
campanella separata accanto all'avatar. Indipendente dal canale push sopra:
non richiede che il dispositivo abbia attivato «Notifiche», ha uno storico persistente in
notifiche_utente (vedi DATABASE.md) con stato letto/non letto, e non è la
stessa cosa delle notifiche smart (quelle restano locali, non salvate a database).
Cinque sorgenti scrivono in notifiche_utente, mai il client:
- Messaggio admin —
notifica-personalizzata.tsinserisce una riga per destinatario in parallelo all'invio push esistente (a tutta la rosa attiva segiocatoreIdè omesso). - Promemoria evento — due job
pg_cron(ogni ora per la finestra delle 24h, ogni 15 minuti per quella delle 3h) generano una notifica per evento imminente, per i convocati (o tutta la rosa attiva se l'evento non ne specifica), deduplicata dal registropromemoria_eventi_generati(M23, DD-037; il vincoloUNIQUE (giocatore_id, evento_id, tipo)dinotifiche_utenteresta come seconda difesa) così un cron che gira più volte non manda doppioni, nemmeno se il giocatore elimina la notifica. Volutamente non c'è una notifica sulla sola creazione dell'evento: conta l'avvicinarsi della data, non il momento in cui è stato messo in calendario. - Turno palloni —
promemoria-palloni.ts(bottone riservato agli admin sulla pagina evento, vedi Turno palloni) inserisce una riga per ciascun avviso calcolato daavvisiPalloniEvento(), in parallelo alla push. - Sollecito presenze —
sollecita-presenze.ts(stesso innesco manuale) inserisce una riga per ciascun giocatore che non ha ancora risposto, in parallelo alla push. - Sondaggio pre-partita —
apri-sondaggio.ts(pulsante riservato agli admin sulla pagina della partita) inserisce una riga di tiposondaggio_caccheper ciascun giocatore attivo, allenatori esclusi (destinatariSondaggio()), in parallelo alla push (M24, DD-038).
Turno palloni, sollecito presenze e sondaggio usano un upsert su (giocatore_id, evento_id, tipo)
invece di un semplice insert: se l'admin preme di nuovo il pulsante per lo stesso evento, la
notifica esistente viene aggiornata (testo e creato_il freschi, letta riportata a false)
invece di duplicarsi o fallire per il vincolo UNIQUE.
Lettura, "segna come letta" ed eliminazione passano dal client Supabase autenticato con RLS
(useNotificheMie()/useSegnaLette()/useEliminaNotifica()), senza una route API dedicata
— stesso pattern di useSalvaEvento() in src/lib/eventi.ts. Aprire il pannello segna tutte
le notifiche del giocatore selezionato come lette; non c'è un pulsante "segna singola"
separato.
Non c'è pulizia automatica delle notifiche vecchie (DD-030): l'unico modo per farle
sparire per sempre è eliminarle una per una, con uno swipe verso sinistra sulla riga
(RigaNotifica in ui-bits.tsx, stessa fisica a molla dello swipe del calendario) o con la
× che compare sopra ogni riga per chi non è su touch. Chi non tocca mai una notifica se la
ritrova per sempre nell'elenco, solo segnata come letta.
Push dei promemoria (M24)
I promemoria a 24 e 3 ore nascono nel database (job pg_cron), che non può firmare né cifrare una
push Web: la manda il worker mailer/, lo stesso che invia le email (WORKER_EMAIL.md,
DD-038).
- Quando il job crea un promemoria, un trigger lo accoda in
notifiche_push_coda(solo i tipievento_promemoria_24heevento_promemoria_3h: le altre notifiche mandano già la push dall'app, e una seconda sarebbe un doppione). - Il worker, a ogni giro, chiama
prendi_push_promemoria(), che restituisce testo e dispositivi iscritti del giocatore, e manda la push a tutti i suoi dispositivi con lo stesso modulo dell'app (src/lib/webpush.server.ts), quindi con le stesse regole di cifratura, urgenza e TTL. - Un solo esito per notifica:
inviatase almeno un dispositivo accetta;riprovacon backoff 1 · 5 · 30 · 120 minuti sugli errori temporanei (429, 5xx, rete), poifallita;fallitasubito su un rifiuto permanente (400, 401, 403, 413: tipicamente chiavi VAPID sbagliate);saltatase le iscrizioni erano tutte scadute o il giocatore non ne ha. - Le iscrizioni che rispondono 404 o 410 vengono eliminate da
push_subscriptions, come fanno le route. - Una push in coda da oltre 3 ore dalla creazione del promemoria si scarta (
saltata): a evento ormai iniziato sarebbe solo rumore. Vale se il worker resta fermo a lungo.
La push dei promemoria dipende solo dai dispositivi iscritti, non dall'interruttore «Email». Se il worker non ha le chiavi VAPID parte lo stesso e manda solo le email, segnalandolo nel log.
Canale email (M22)
Ogni riga di notifiche_utente viene inviata anche per email da un worker esterno, con
un interruttore «Email» per account in Profilo → Opzioni: notifiche-email.md.
Non cambia nulla di quanto descritto sopra: push, notifiche smart e centro notifiche restano
indipendenti e le sorgenti scrivono notifiche_utente come prima. Il certificato in
scadenza resta una card in Home e non genera mail (DD-035).
Limiti noti
-
Non ci sono preferenze granulari (solo palloni / solo presenze / solo smart): un dispositivo è iscritto o no. Separare i canali richiederebbe schema e UI dedicati.
-
La tabella
promemoria_pushè rimasta nel database ma non la usa più nessuno (DD-026): va eliminata con una migrazione alla prossima occasione. -
Un 2xx dal server push non significa consegnato. FCM accetta con 201 anche verso registrazioni scadute e poi butta via il messaggio, senza il 404/410 che farebbe pulire
push_subscriptions. Il conteggio "inviate a N dispositivi" va letto come "accettate da N server push", non come "arrivate a N telefoni". -
Compatibilità iOS/Safari non gestita esplicitamente nel codice (nessun branch dedicato): serve l'installazione da schermata Home per funzionare, ma l'app non lo segnala esplicitamente. È il primo sospetto quando una notifica non arriva ad app chiusa su iPhone.
-
Su Android non riceve la webapp: riceve il browser. Il WebAPK è solo l'identità con cui la notifica viene mostrata; la connessione con i server push la tiene Chrome, tramite Google Play Services. Se Android non può avviare Chrome, il messaggio resta in coda e compare tutto insieme al lancio successivo — il sintomo classico è «arriva solo quando riapro l'app». Un 201 dal servizio push non lo distingue in alcun modo da una consegna riuscita.
Verificato sul campo (settembre 2026, Motorola): con Chrome vivo in secondo piano la push arriva ad app chiusa e schermo bloccato, WebAPK compreso — quindi server, cifratura, service worker, permesso notifiche e canale erano già corretti. L'unica condizione che fallisce è Chrome non in esecuzione. La cura sta in Impostazioni → App → Chrome → Batteria → «Senza restrizioni», più Impostazioni → Batteria → «Batteria adattiva» disattivata. Mettere «Senza restrizioni» solo su CrAPP non basta e depista.
Come misurarlo invece di indovinare:
chrome://gcm-internalssul telefono, sezione «Receive Message Log». Se la riga porta l'orario dell'invio, il messaggio era arrivato e non è stato mostrato (permesso o canale); se porta l'orario in cui si è riaperta l'app, non era stato consegnato (risveglio, quindi batteria). Attenzione: tenere quella scheda aperta tiene Chrome vivo, quindi falsa la prova stretta — per quella, nessuna scheda aperta e Chrome tolto dai recenti. -
Su Motorola verificare anche le restrizioni del browser che ha installato CrAPP e, dove presente, Impostazioni → Batteria → Ottimizzazione standby app. Il produttore documenta la limitazione dei processi in background (guida Motorola). È una possibile causa del sintomo, non una diagnosi verificata sul dispositivo: il codice web non può rimuovere questi vincoli. La verifica richiede un invio da un altro dispositivo mentre CrAPP è chiusa e lo schermo del Motorola è bloccato. Il pulsante di prova invia subito, quindi da solo non dimostra la ricezione in background.
-
Le notifiche smart dipendono da un service worker già registrato: se il giocatore non ha mai attivato le push,
notificaSistema()non ha unrega cui appoggiarsi e la notifica locale non viene mai mostrata, anche con permesso concesso. -
Il payload cifrato non può superare i ~4 KB: i testi attuali stanno larghi, ma un messaggio molto lungo verrebbe rifiutato dal servizio push.
Evoluzioni possibili
- Preferenze per canale (palloni, solleciti, smart), se servono davvero alla squadra.
- Eliminare
promemoria_pushcon una migrazione. - Gestire esplicitamente il caso iOS (messaggio se l'app non è installata da Home).