diff --git a/CHECKLIST-COMPLIANCE-MERCHANT.md b/CHECKLIST-COMPLIANCE-MERCHANT.md index 125e0e9..55303bc 100644 --- a/CHECKLIST-COMPLIANCE-MERCHANT.md +++ b/CHECKLIST-COMPLIANCE-MERCHANT.md @@ -11,7 +11,7 @@ in capo al merchant. Da consegnare col progetto. - **Pulsante/funzione di recesso sempre accessibile** (footer o dove scelto), **guest**, senza login. - Raccolta della **dichiarazione inequivocabile** (nome, n. ordine, email, testo) con **conferma dedicata a 2 step** (nessun dark pattern). - Registrazione con **timestamp di TRASMISSIONE** (onere della prova, Art. 54-bis). -- **Ricevuta su supporto durevole** al consumatore: dichiarazione + timestamp + avviso di legge - **sempre inviata, contenuto legale non modificabile** dal merchant. +- **Ricevuta su supporto durevole** al consumatore: dichiarazione + timestamp + avviso di legge - **sempre inviata, contenuto legale non modificabile** dal merchant. *(L'app la invia sempre; la CONSEGNA dipende pero' dal provider SMTP e dalla reputazione del dominio mittente configurati dal merchant - vedi §3.)* - **Audit log immutabile** delle richieste ed eventi. - Coesistenza col reso/rimborso **nativo Shopify** (crea il Reso per gli ordini evasi). @@ -32,6 +32,7 @@ in capo al merchant. Da consegnare col progetto. - Gestire **resi parziali** e rimborsi proporzionali (pannello ordini Shopify). - Definire la **politica di reso** (indirizzo, spese, integrita' prodotto) coerente con quanto mostra l'app. - **Verifica manuale della data di consegna** se il corriere non trasmette l'evento a Shopify (in tal caso la finestra automatica non blocca). +- **Configurare un provider SMTP e autenticare il dominio mittente** (SPF, DKIM, DMARC) presso quel provider. Senza SMTP la ricevuta non parte affatto; senza autenticazione del dominio finisce facilmente in spam. In entrambi i casi il supporto durevole non raggiunge il consumatore. Consigliato: mittente dedicato (`no-reply@` / `recesso@`), meglio su un **sottodominio** dedicato alla posta transazionale, mai una casella umana. Verificare con il bottone **"Invia email di prova"** in Impostazioni → Email (SMTP). - Privacy policy, condizioni di vendita, gestione dati (GDPR) del negozio. - Configurare le **esclusioni Art. 59** solo per prodotti realmente esclusi (mala-config = negare il diritto a torto). diff --git a/app/app/lib/mailer.server.ts b/app/app/lib/mailer.server.ts index 38908d0..f3703d4 100644 --- a/app/app/lib/mailer.server.ts +++ b/app/app/lib/mailer.server.ts @@ -10,6 +10,7 @@ * SMTP_SECURE("true"/"false"), MAIL_FROM. DEV: Mailpit localhost:1025. */ +import { randomUUID } from "node:crypto"; import nodemailer from "nodemailer"; import { renderReceiptHtml, @@ -66,16 +67,28 @@ function mailFrom(smtp?: SmtpConfig | null): string { type Transport = NonNullable>; +/** + * Message-ID allineato al dominio del mittente. Un Message-ID con dominio + * incoerente (di default: l'hostname del container) e' un segnale negativo + * per i filtri antispam. + */ +function makeMessageId(from: unknown): string | undefined { + if (typeof from !== "string") return undefined; + const m = from.match(/@([^>\s]+)/); + return m ? `<${randomUUID()}@${m[1]}>` : undefined; +} + /** Invio con retry (backoff lineare). Riduce le ricevute perse per glitch SMTP. */ async function trySend( transport: Transport, message: Parameters[0], attempts = 3, ) { + const msg = { messageId: makeMessageId(message.from), ...message }; let lastErr: unknown; for (let i = 0; i < attempts; i++) { try { - return await transport.sendMail(message); + return await transport.sendMail(msg); } catch (e) { lastErr = e; if (i < attempts - 1) {