Deliverability: Message-ID allineato al dominio mittente + checklist dominio
- trySend(): genera un Message-ID nel dominio del From (il default di nodemailer usa l'hostname del container -> dominio incoerente, segnale antispam). - CHECKLIST-COMPLIANCE-MERCHANT: l'autenticazione del dominio mittente (SPF/DKIM/ DMARC) e la configurazione SMTP diventano obblighi espliciti del merchant; senza, la ricevuta durevole non raggiunge il consumatore. Chiarito che l'app la invia sempre ma la consegna dipende dal provider/dominio del merchant.
This commit is contained in:
@@ -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.
|
- **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).
|
- 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).
|
- 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.
|
- **Audit log immutabile** delle richieste ed eventi.
|
||||||
- Coesistenza col reso/rimborso **nativo Shopify** (crea il Reso per gli ordini evasi).
|
- 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).
|
- Gestire **resi parziali** e rimborsi proporzionali (pannello ordini Shopify).
|
||||||
- Definire la **politica di reso** (indirizzo, spese, integrita' prodotto) coerente con quanto mostra l'app.
|
- 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).
|
- **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.
|
- 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).
|
- Configurare le **esclusioni Art. 59** solo per prodotti realmente esclusi (mala-config = negare il diritto a torto).
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
* SMTP_SECURE("true"/"false"), MAIL_FROM. DEV: Mailpit localhost:1025.
|
* SMTP_SECURE("true"/"false"), MAIL_FROM. DEV: Mailpit localhost:1025.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { randomUUID } from "node:crypto";
|
||||||
import nodemailer from "nodemailer";
|
import nodemailer from "nodemailer";
|
||||||
import {
|
import {
|
||||||
renderReceiptHtml,
|
renderReceiptHtml,
|
||||||
@@ -66,16 +67,28 @@ function mailFrom(smtp?: SmtpConfig | null): string {
|
|||||||
|
|
||||||
type Transport = NonNullable<ReturnType<typeof buildTransport>>;
|
type Transport = NonNullable<ReturnType<typeof buildTransport>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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. */
|
/** Invio con retry (backoff lineare). Riduce le ricevute perse per glitch SMTP. */
|
||||||
async function trySend(
|
async function trySend(
|
||||||
transport: Transport,
|
transport: Transport,
|
||||||
message: Parameters<Transport["sendMail"]>[0],
|
message: Parameters<Transport["sendMail"]>[0],
|
||||||
attempts = 3,
|
attempts = 3,
|
||||||
) {
|
) {
|
||||||
|
const msg = { messageId: makeMessageId(message.from), ...message };
|
||||||
let lastErr: unknown;
|
let lastErr: unknown;
|
||||||
for (let i = 0; i < attempts; i++) {
|
for (let i = 0; i < attempts; i++) {
|
||||||
try {
|
try {
|
||||||
return await transport.sendMail(message);
|
return await transport.sendMail(msg);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
lastErr = e;
|
lastErr = e;
|
||||||
if (i < attempts - 1) {
|
if (i < attempts - 1) {
|
||||||
|
|||||||
Reference in New Issue
Block a user