La chat di gruppo che gira durante il matrimonio
11 aprile 2026

C'è una differenza fondamentale tra il gioco delle scommesse di ReD Sposi e la chat.
Le scommesse sono asincrone. Le domande stanno lì, gli invitati rispondono quando vogliono, la classifica si aggiorna in tempo reale ma non c'è urgenza. Se il server va giù per dieci minuti durante la settimana prima del matrimonio, nessuno se ne accorge.
La chat è diversa. È live. Gli invitati la usano il giorno stesso, durante il ricevimento, mentre io sono in frac a fare il giro dei tavoli. Se qualcosa si rompe quel giorno, non posso aprire il laptop. Non fisicamente, non praticamente, non emotivamente.
Questa vincolo — deve funzionare autonomamente in una finestra di tempo precisa, senza possibilità di intervento — è stato il criterio di progettazione principale.
Architettura base
La chat è una room Socket.IO. Tutti gli invitati autenticati entrano nella stessa room al login; i messaggi vengono emessi da un client, rimbalzano dal server, e arrivano a tutti gli altri connessi.
// server: join alla room e broadcast
io.on("connection", (socket) => {
const guestId = socket.data.guestId;
socket.join("wedding-chat");
socket.on("message:send", async (data) => {
const message = await Message.create({
guestId,
text: data.text,
createdAt: new Date(),
});
io.to("wedding-chat").emit("message:new", {
_id: message._id,
guestId,
guestName: socket.data.guestName,
text: message.text,
createdAt: message.createdAt,
});
});
});
Niente di esotico. La complessità non è nell'architettura — è nei casi limite.
Persistenza: chi arriva tardi vede la storia
Se un invitato apre la chat alle 22 durante il ballo, deve vedere i messaggi delle ore precedenti — non entrare in una chat che sembra vuota.
I messaggi vengono salvati su MongoDB a ogni invio. Quando un client si connette alla chat, fa una richiesta REST per caricare gli ultimi N messaggi prima di sottoscriversi agli eventi Socket.IO.
// client: carica storia poi ascolta
const loadChat = async () => {
const history = await api.get("/messages?limit=50");
setMessages(history.data);
socket.on("message:new", (msg) => {
setMessages((prev) => [...prev, msg]);
});
};
L'ordine importa: prima la storia REST, poi l'ascolto Socket.IO. Se fai il contrario, rischi una race condition in cui un messaggio arriva via socket prima che la storia sia caricata, e quando la storia arriva sovrascrive il messaggio live.
Riconnessione: gli invitati si muovono
Un ricevimento non è un ufficio. Gli invitati si muovono tra sala, giardino, e pista da ballo. Le connessioni cadono — 4G che prende male in certi angoli della location, WiFi del venue che non regge, telefoni che vanno in sleep.
Socket.IO gestisce la riconnessione automaticamente, ma bisogna gestire cosa succede quando un client si riconnette dopo un'assenza. I messaggi arrivati nel frattempo non vengono riemessi — il client deve recuperarli.
Ho risolto salvando il timestamp dell'ultimo messaggio ricevuto sul client, e al reconnect richiedendo i messaggi successivi a quella data:
socket.on("connect", async () => {
if (lastMessageAt) {
const missed = await api.get(`/messages?after=${lastMessageAt}`);
if (missed.data.length > 0) {
setMessages((prev) => [...prev, ...missed.data]);
}
}
});
Non è real-time puro — c'è una finestra di qualche secondo in cui un messaggio potrebbe non apparire — ma per una chat di gruppo a bassa frequenza è più che sufficiente.
Notifiche push e Socket.IO: coesistenza
Gli invitati non stanno tutti nella schermata della chat tutto il tempo. Stanno nell'app a guardare la galleria, a fare scommesse, o hanno il telefono in tasca.
Il problema: Socket.IO arriva solo se il client è connesso e in foreground. Se l'app è in background, la connessione viene sospesa dal sistema operativo.
La soluzione è duplicare la notifica: quando arriva un messaggio, il server emette l'evento Socket.IO e manda una push notification a tutti gli invitati che non sono attivamente connessi alla chat.
socket.on("message:send", async (data) => {
const message = await Message.create({ ... });
// broadcast a tutti i connessi
io.to("wedding-chat").emit("message:new", message);
// push a chi non è nella chat
const connectedIds = getConnectedGuestIds("wedding-chat");
const offlineGuests = await Guest.find({
_id: { $nin: connectedIds },
pushToken: { $exists: true },
});
await sendPushNotifications(offlineGuests, {
title: message.guestName,
body: message.text,
data: { screen: "chat" },
});
});
getConnectedGuestIds tiene traccia di chi ha una socket attiva nella room. Non è preciso al millisecondo — ci può essere un piccolo overlap in cui qualcuno riceve sia la push che il messaggio via socket — ma è accettabile.
La parte sociale che semplifica tutto
Costruire una chat per persone che si conoscono tutte tra loro rimuove interi strati di complessità.
Non c'è moderazione. Non c'è report/block. Non c'è anonimato. Non c'è spam. Il nome dell'invitato è reale, ogni messaggio è firmato con il nome che gli ho assegnato al momento della creazione del codice invito. Se qualcuno scrive qualcosa di inappropriato — ipotesi teorica, ma la gestisco lo stesso — lo so già chi è.
Questo significa che la chat è essenzialmente un sistema pub/sub con persistenza e un layer di autenticazione già fatto. Niente algoritmi di moderazione, niente rate limiting aggressivo, niente sistema di blocco.
La complessità sociale è gestita dal contesto reale, non dal software.
Cosa mi preoccupava di più
Il punto di maggiore rischio non era tecnico — era l'ora di punta.
Immagina: la cerimonia finisce, tutti tirano fuori il telefono, trenta persone aprono l'app contemporaneamente, ognuna carica la storia dei messaggi, ognuna stabilisce una connessione Socket.IO, e nel giro di cinque minuti iniziano a mandarsi messaggi a raffica.
Ho fatto un test di carico minimale — non automatizzato, solo io che apro l'app da sei dispositivi diversi contemporaneamente — e il server ha retto senza problemi. Per qualche centinaio di invitati su un singolo VPS Node.js con Socket.IO, il volume non è mai un problema reale.
Il vero rischio era il database: tante scritture parallele su MongoDB in un lasso di tempo breve. Ho aggiunto un indice su createdAt per le query di storia, e ho verificato che le scritture non diventassero un collo di bottiglia con explain(). Non lo erano.
Essere il groom e lo sviluppatore
C'è qualcosa di strano nel costruire un sistema che girerà in un momento in cui non puoi controllarlo.
Per tutti gli altri prodotti che ho costruito, sono sempre raggiungibile. Se Barba Studio ha un problema, apro il laptop. Se il gestionale smette di funzionare, mi connetto al server. La presenza è sempre possibile anche se non sempre immediata.
Il 2 agosto non è così. Quella finestra di tempo — dalle undici di mattina a mezzanotte — è inaccessibile da sviluppatore. È accessibile solo da sposo.
Costruire con questa consapevolezza ti spinge verso un tipo di semplicità diverso da quello economico. Non semplifichi perché vuoi finire prima. Semplifichi perché ogni pezzo in più è un pezzo che potrebbe rompersi quando non puoi intervenire. La semplicità diventa una forma di rispetto per il giorno stesso.
La chat funziona. L'ho testata. Agosto arriva comunque.