El boletín, con doble confirmación
El backend guarda la lista de quienes quieren recibir tu boletín.
El backend guarda la lista de quienes quieren recibir tu boletín. Nadie entra por escribir un correo: entra cuando el dueño de esa dirección pulsa el enlace del correo de confirmación.
Las campañas no se mandan desde aquí: la lista se sincroniza con un servicio de correo (Brevo, Mailchimp o Resend) y las campañas se escriben allí. Lee «Conectar un servicio de correo».
Por qué en dos pasos
Ninguna ley exige el doble opt-in, pero el RGPD (artículo 7.1) obliga a poder demostrar que el dueño de la dirección consintió. Un formulario que solo apunta el correo no prueba nada: cualquiera puede escribir el de otro.
Por eso cada suscripción guarda:
- el texto exacto que se enseñó junto a la casilla, y su versión;
- la fecha, la IP de quien lo pidió, la IP del escaparate que pasó la petición y el navegador;
- la fecha en que el dueño del correo pulsó el enlace de confirmación.
Sin el texto del consentimiento y su versión, la suscripción se rechaza.
Cómo se apunta alguien
El tema llama a newsletter.subscribe(email, consent) del
contrato de comercio, con el texto
enseñado y su versión, el idioma y, si los tiene, dónde estaba el formulario, la
IP y el navegador.
- La suscripción se guarda como pendiente y sale un correo con un enlace.
- El enlace es
/boletin/confirmar?token=…sobre la dirección deSTOREFRONT_URL(oPCC_TIENDA_URL). Es una ruta de entrada sin idioma: el escaparate la reparte al idioma de quien entra. Sin esa variable el enlace no tiene dominio, así que ponla. - El enlace vale una vez y durante siete días. Al pulsarlo, el tema
llama a
newsletter.confirm(token)y la suscripción pasa a confirmada. Si ha caducado o ya se usó,confirmdevuelvefalse.
El correo sale en el idioma con el que llegó la suscripción: inglés o español. Cualquier otro idioma, o ninguno, recibe inglés.
Lo que el formulario nunca desvela
La respuesta es siempre la misma —«pendiente»— tanto si la dirección era nueva como si ya estaba pendiente o confirmada. Si dijera «ya estás suscrito», el formulario le diría a cualquiera quién está en tu lista.
Y pedirlo otra vez no sirve para llenarle el buzón a nadie: una dirección pendiente no recibe otro correo hasta diez minutos después del último. Una dirección ya confirmada no recibe nada.
Darse de baja
Cada suscripción tiene su propio token de baja. Darse de baja no pide nada más
que ese enlace: tiene que ser tan fácil como apuntarse (RGPD, artículo 7.3). El
tema llama a newsletter.unsubscribe(token), que siempre contesta que sí,
aunque la persona ya se hubiera ido.
Quien se da de baja y se vuelve a apuntar empieza de cero: pendiente otra vez y correo de confirmación nuevo. El consentimiento viejo no vale para la suscripción nueva.
Sacar la lista
El panel tiene una pantalla de Boletín (Clientes → Boletín) con los tres recuentos, la lista, la prueba del consentimiento de cada persona, un botón para dar de baja y la exportación a CSV.
Desde la API de gestión, con cualquier cuenta del equipo:
GET /gestion/boletin # solo los confirmados, con su token de baja
GET /gestion/boletin/suscritos # la lista entera con la prueba del consentimiento
GET /gestion/boletin/csv # lo mismo, en CSV/gestion/boletin devuelve solo los confirmados, en el orden en que
confirmaron. Las direcciones pendientes y las dadas de baja no salen nunca.
Conectar un servicio de correo
Las campañas no se mandan desde aquí. Lo que hace la tienda es que su lista y la del servicio de correo digan lo mismo, en los dos sentidos. Esa sincronización es donde esto se rompe siempre, y es la parte que tiene consecuencias legales: alguien que se da de baja en el servicio y sigue recibiendo, o al revés.
La pantalla es Boletín → Conectar un servicio (/boletin/conector) y solo
la abre un Administrador: son llaves de un tercero. Se guardan cifradas con
el secreto del servidor (PCC_CLAVE_CIFRADO o, si falta, JWT_SECRET); sin
ninguno de los dos, guardarlas se niega con un 503 en vez de cifrarlas con algo
adivinable.
Qué servicios, y por qué
| Servicio | Los datos viven en | Avisos de baja | Para quién |
|---|---|---|---|
| Brevo | Unión Europea | unsubscribed, hardBounce, spam | La opción por defecto en Europa: empresa francesa, sin marco de transferencia de por medio, y el único que distingue las tres cosas |
| Mailchimp | Estados Unidos | unsubscribe, cleaned (rebote duro) | Enchufarse a una audiencia que el dueño ya tiene |
| Resend | Estados Unidos | contact.updated, email.complained, email.bounced, suppression.added | Tiendas que ya mandan su correo con él |
No hay ningún estándar común para esto, y no merece la pena esperarlo:
Mailchimp modela un enum de estados, Brevo un booleano de lista negra y Resend
un booleano global. Lo único parecido a un estándar que hay cerca es la
RFC 8058 —la cabecera
List-Unsubscribe de un clic—, y va del envío del correo, no de sincronizar
listas. Lo que los tres sí comparten es exactamente el tamaño del contrato del
conector: el correo como identificador, un estado que al final es «recibe / no
recibe» y unos atributos sin tipar.
Solo puede haber un servicio conectado a la vez. Dos servicios con la misma lista son dos campañas al mismo buzón y dos sitios donde darse de baja a medias. Cambiar de servicio vuelve a mandarlo todo, porque la lista del servicio nuevo no sabe nada de lo que se sincronizó con el anterior.
Qué va en cada sentido
- Alta confirmada aquí → alta allí. Nunca antes de confirmar: lo que da valor (y legalidad) a la lista es el consentimiento confirmado.
- Baja aquí → baja allí. Por enlace, desde el panel, da igual.
- Baja allí → baja aquí. Por el aviso, o por el repaso que corre cada 30 minutos.
- Un rebote duro o una queja de spam también son una baja, y se guardan como
tal (
motivo_baja:rebote,queja), porque seguir escribiendo a un buzón que no existe —o a quien te ha marcado como spam— hunde la reputación del dominio y con ella el correo de los pedidos.
Un alta hecha en el servicio no se trae. El consentimiento vive aquí, y una dirección sin su prueba no pinta nada en esta lista.
Y un alta nunca resucita a quien se dio de baja en el servicio. Mailchimp directamente lo prohíbe (400, «Member In Compliance State»); en Brevo y Resend se podría, pero sería volver a escribir a quien dijo que no desde el otro lado.
Qué pasa si el servicio está caído
Una baja no se puede perder, así que nada se manda con un fetch a pelo
dentro de la petición del escaparate. Hay dos caminos, y los dos hacen falta:
- La cola. Confirmar o darse de baja escribe un suceso
boletin.altaoboletin.bajaen la misma base, justo después del cambio. El repartidor lo lleva al servicio y reintenta con esperas cada vez más largas. Si se rinde a los seis intentos, el suceso no se borra: se aparta, y desde el panel se puede reintentar. - El repaso (
repasar-boletin, cada 30 minutos). Cada dirección guarda lo último que el servicio confirmó que sabe (sincronizado_estado) al lado de lo que debería saber (estado). La diferencia es el trabajo pendiente, y el repaso lo arregla, venga de una cola rendida, de un servicio que estuvo caído media tarde o de un cambio de servicio.
El repaso pregunta primero y empuja después, y el orden importa: empujar antes podría devolver a las campañas a quien acababa de darse de baja en el servicio y cuya baja todavía no habíamos traído.
La dirección de avisos
El panel enseña la dirección que hay que pegar en el servicio, y lleva su propia clave. Esa clave es la única protección que tiene un servicio que no firma sus avisos (Brevo no los firma) y la primera puerta para los que sí. Trátala como una contraseña.
Donde el servicio firma, se comprueba además la firma: Mailchimp con
X-Mailchimp-Signature (HMAC-SHA256 sobre marca.cuerpo, rechazada si tiene
más de cinco minutos) y Resend a través de Svix (svix-id, svix-timestamp,
svix-signature, con el secreto whsec_). Un aviso que no se puede comprobar
no da de baja a nadie.
La dirección se construye con PCC_BACKEND_URL; PCC_BOLETIN_WEBHOOK_URL solo
hace falta si los avisos tienen que llegar a otro sitio.
Lo que sigue faltando
- El envío. Aquí no hay campañas, ni plantillas, ni botón de enviar: escribes el boletín en el servicio de correo y lo mandas desde allí. Sin servicio conectado, exportas la lista y usas tu herramienta.
- El enlace de baja de cada correo. Si mandas desde un servicio conectado,
usa su propio enlace de baja y el aviso volverá hasta aquí. Si mandas a mano,
constrúyelo con el token de cada persona, por ejemplo
/boletin/baja?token=…en tu escaparate. - Los temas de fábrica no lo conectan. La sección de boletín de
basemanda a una dirección externahttps://que pones en sus ajustes (sin ella, la sección no se dibuja) y, en la demo, solo enseña un mensaje y no guarda nada;mascotasycinematograficodibujan el formulario sin mandarlo a ningún sitio;dulce-obradordice «¡Suscripción confirmada!» sin guardar nada. Ninguno tiene la página de confirmar ni la de baja. Un tema que quiera un boletín de verdad tiene que llamar asubscribe,confirmyunsubscribe.
Ver también
Traer tu tienda de Shopify o WooCommerce
Traerte el catálogo, los clientes y el historial de pedidos de tu tienda actual sin perder el posicionamiento que ya tenías en Google.
Pagar a autores y vendedores
Antes de mover dinero de terceros, consúltalo con un asesor. Esta guía explica qué hace el sistema; qué te exige la ley a ti, no.