Saltar al contenido
Pidgely
  • entregabilidad
  • cumplimiento
  • gmail

Baja con un clic (RFC 8058): cómo implementarla bien

Cómo implementar la baja con un clic según RFC 8058: headers List-Unsubscribe y List-Unsubscribe-Post, endpoint POST, firma DKIM y la trampa de los escáneres.

Equipo PidgelyPublicado el 5 min de lecturaVersión en Markdown

En este artículo
  1. El problema que resuelve
  2. Los headers
  3. Lo que hace el cliente
  4. Implementación del endpoint
  5. La trampa de los escáneres de enlaces
  6. Y el mailto:
  7. Qué mensajes la necesitan
  8. Errores comunes
  9. Checklist
  10. En Pidgely
  11. Preguntas frecuentes

La baja con un clic definida en RFC 8058 permite que Gmail, Yahoo y otros clientes muestren un botón "Cancelar suscripción" que da de baja al usuario sin abrir ninguna página. Se implementa con dos headers, List-Unsubscribe (con una URL HTTPS) y List-Unsubscribe-Post: List-Unsubscribe=One-Click, ambos firmados con DKIM, y un endpoint que procesa la baja al recibir una petición POST. Desde febrero de 2024 es obligatoria para el correo comercial de los remitentes masivos según las directrices de Google y Yahoo.

El problema que resuelve

El header List-Unsubscribe existe desde RFC 2369 (1998). Permitía indicar una dirección mailto: o una URL para darse de baja, pero no definía qué debía pasar al abrir esa URL. Muchos remitentes la llevaban a una página con formularios, inicios de sesión o encuestas, así que los clientes de correo no podían ofrecer una baja automática fiable.

RFC 8058 añade una señal explícita: si el mensaje incluye List-Unsubscribe-Post, el cliente puede hacer un POST a la URL y el remitente se compromete a dar de baja sin más pasos. El resultado para el usuario es un botón nativo en la interfaz. Para ti, menos quejas: quien quiere dejar de recibir tus correos tiene una alternativa más cómoda que "Marcar como spam".

Los headers

text
List-Unsubscribe: <https://example.com/unsubscribe/one-click?t=eyJsIjoibmV3cyIsInIiOiI4ZjJhIn0.c2ln>,
 <mailto:unsubscribe+8f2a@example.com?subject=unsubscribe>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

Requisitos de RFC 8058:

  • Al menos una URI HTTPS en List-Unsubscribe. El mailto: es opcional, pero útil para clientes que no soportan el POST.
  • List-Unsubscribe-Post con el valor exacto List-Unsubscribe=One-Click.
  • Firma DKIM válida que cubra ambos headers (deben aparecer en la etiqueta h= del DKIM-Signature). Sin esa firma, un intermediario podría inyectar una URL maliciosa y el cliente no debería ofrecer el botón.
  • La URL debe identificar al destinatario y la lista por sí sola, normalmente mediante un token opaco.

Lo que hace el cliente

Cuando el usuario pulsa el botón, el cliente de correo (o su backend) envía:

HTTP
POST /unsubscribe/one-click?t=eyJsIjoibmV3cyIsInIiOiI4ZjJhIn0.c2ln HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 26

List-Unsubscribe=One-Click

Ese POST no lleva cookies ni credenciales del usuario. Tu servidor debe:

  1. Validar el token.
  2. Registrar la baja.
  3. Responder con un 2xx.

Nada más. No hay confirmación, ni redirección a una página, ni encuesta.

Implementación del endpoint

Un ejemplo en Node.js con Express:

JavaScript
import express from "express";
import crypto from "node:crypto";

const app = express();
const SECRET = process.env.UNSUB_SECRET;

function verifyToken(token) {
  const [payload, sig] = token.split(".");
  if (!payload || !sig) return null;
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(payload)
    .digest("base64url");
  const a = Buffer.from(sig);
  const b = Buffer.from(expected);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return null;
  return JSON.parse(Buffer.from(payload, "base64url").toString());
}

// POST: ejecuta la baja (RFC 8058)
app.post(
  "/unsubscribe/one-click",
  express.urlencoded({ extended: false }),
  async (req, res) => {
    const data = verifyToken(String(req.query.t || ""));
    if (!data) return res.status(400).end();
    await suppress({ list: data.l, recipient: data.r, source: "one-click" });
    res.status(200).end();
  }
);

// GET: nunca da de baja, solo muestra una página con un botón
app.get("/unsubscribe/one-click", (req, res) => {
  res.send(renderConfirmationPage(req.query.t));
});

Detalles importantes:

  • Firma el token con HMAC y compara en tiempo constante. Un token predecible (como un ID secuencial) permitiría a cualquiera dar de baja a otros.
  • Incluye la lista en el token, para dar de baja solo de esa lista y no de todo, si así lo ofreces. Lo que no debes hacer es dar de baja de menos de lo que el usuario espera.
  • Haz la operación idempotente. El cliente puede repetir el POST. Dar de baja dos veces no debe fallar.
  • Responde rápido. Si la escritura es lenta, encola la baja, pero confírmala en tu sistema de supresión lo antes posible.
  • Acepta también multipart/form-data, que RFC 8058 permite como alternativa.

La trampa de los escáneres de enlaces

Los filtros corporativos, antivirus y algunos clientes de correo visitan los enlaces de los mensajes para analizarlos antes de que el usuario los vea. Si tu URL de baja ejecuta la baja con un simple GET, esos escáneres darán de baja a usuarios que nunca lo pidieron. Es un problema conocido y difícil de detectar: verás bajas "fantasma" concentradas en dominios empresariales, a menudo segundos después de la entrega.

Por eso RFC 8058 usa POST. La regla es:

  • GET → página de confirmación con un botón que envía un POST (formulario simple, sin JavaScript obligatorio).
  • POST → baja efectiva.

El enlace de baja visible en el cuerpo del mensaje puede apuntar a la misma URL: el usuario llega por GET, ve la página y confirma con un botón. Así cumples con el enlace visible que piden Google y Yahoo sin exponerte a los escáneres.

Y el mailto:

Si incluyes mailto:, debes procesar los correos que lleguen a esa dirección: extraer el token del local-part (unsubscribe+8f2a@...), validarlo y aplicar la baja. Ignorar ese buzón es un incumplimiento silencioso. Algunos clientes antiguos solo usan esta vía.

Qué mensajes la necesitan

Según las directrices de Google y Yahoo, la baja con un clic es obligatoria en mensajes comerciales y promocionales de remitentes masivos. Los transaccionales (recuperación de contraseña, recibos, alertas de seguridad) no la necesitan, y añadirla puede ser confuso: el usuario no puede "darse de baja" de un restablecimiento de contraseña.

Errores comunes

  • Headers no firmados por DKIM. El botón no aparece o Gmail ignora el header.
  • URL HTTP en lugar de HTTPS.
  • Baja por GET, con bajas fantasma por escáneres.
  • POST que redirige a una página (302) o exige cookies.
  • Token reutilizable para cualquier destinatario o adivinable.
  • Baja aplicada solo en la herramienta de marketing, mientras otro sistema sigue enviando a esa dirección. La supresión debe ser central.
  • Buzón del mailto: sin procesar.
  • Procesar en lotes semanales, por encima del plazo de dos días.

Checklist

  • [ ] List-Unsubscribe con URL HTTPS (y opcionalmente mailto:).
  • [ ] List-Unsubscribe-Post: List-Unsubscribe=One-Click.
  • [ ] Ambos headers incluidos en h= de la firma DKIM.
  • [ ] Token firmado con HMAC, por destinatario y lista.
  • [ ] POST idempotente que responde 2xx sin redirecciones.
  • [ ] GET muestra confirmación, nunca ejecuta la baja.
  • [ ] Buzón mailto: procesado automáticamente.
  • [ ] Baja propagada a la lista de supresión central al instante.
  • [ ] Enlace de baja visible en el cuerpo.

En Pidgely

Pidgely añade los headers de RFC 8058 a los mensajes de los streams de marketing, los incluye en la firma DKIM del dominio y aloja el endpoint de baja con protección frente a escáneres. Cada baja entra en la lista de supresión y genera un webhook firmado para que tu aplicación pueda sincronizar sus preferencias.

Preguntas frecuentes

¿Qué headers necesito para la baja con un clic?

List-Unsubscribe con al menos una URL HTTPS y List-Unsubscribe-Post con el valor exacto List-Unsubscribe=One-Click. Ambos deben estar cubiertos por una firma DKIM válida.

¿Puedo dar de baja al usuario cuando abre la URL con GET?

No es buena idea. Los escáneres de seguridad y los filtros visitan los enlaces con GET; si eso da de baja, perderás suscriptores que no lo pidieron. Solo el POST debe ejecutar la baja.

¿En cuánto tiempo debo procesar la baja?

Google y Yahoo piden procesarla en un plazo de dos días. Técnicamente lo ideal es que el POST la aplique de inmediato.

¿El endpoint puede pedir confirmación o inicio de sesión?

No. RFC 8058 exige que el POST complete la baja sin interacción adicional, sin cookies ni redirecciones que el cliente tenga que seguir.

Equipo Pidgely

Publicado el

Construimos Pidgely, la API de email que te lleva a la bandeja de entrada. Escribimos sobre lo que aprendemos entregando emails, citando los estándares y las pautas de los proveedores detrás de cada afirmación.

También disponible en: