Pular para o conteúdo
Pidgely
  • entregabilidade
  • conformidade
  • boas-praticas

Descadastro de um clique (RFC 8058): como implementar

Como implementar o descadastro de um clique com List-Unsubscribe e List-Unsubscribe-Post (RFC 8058), e como evitar descadastros falsos por scanners.

Time PidgelyPublicado em 5 min de leituraVersão em Markdown

Neste artigo
  1. Os dois headers
  2. O que o cliente de email envia
  3. Implementação passo a passo
  4. A armadilha dos scanners de link
  5. Outras armadilhas
  6. Como testar
  7. Checklist
  8. Na Pidgely
  9. Perguntas frequentes

O descadastro de um clique, definido na RFC 8058, permite que o próprio cliente de email (Gmail, Yahoo, Apple Mail e outros) descadastre o usuário com um botão, enviando um POST HTTPS para uma URL que você informa no header List-Unsubscribe, acompanhado do header List-Unsubscribe-Post: List-Unsubscribe=One-Click. O seu servidor recebe o POST e remove o endereço sem pedir login nem confirmação. Desde 2024, Google e Yahoo exigem esse mecanismo em mensagens comerciais de remetentes em massa, com o pedido honrado em até dois dias.

Os dois headers

text
List-Unsubscribe: <https://example.com.br/descadastro/eyJ1IjoiOWYyYyJ9.c2ln>,
 <mailto:descadastro@example.com.br?subject=9f2c>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
  • List-Unsubscribe (RFC 2369) lista uma ou mais URIs entre < >. Para o um clique, pelo menos uma precisa ser https:. O mailto: é opcional e funciona como alternativa para clientes que não suportam POST.
  • List-Unsubscribe-Post (RFC 8058) tem sempre o valor exato List-Unsubscribe=One-Click. Ele sinaliza que a URL HTTPS aceita o POST de um clique.

A RFC 8058 também exige que a mensagem tenha uma assinatura DKIM válida que cubra os dois headers. Sem isso, um intermediário poderia trocar a URL. Confira o h= do DKIM-Signature:

text
DKIM-Signature: v=1; a=rsa-sha256; d=example.com.br; s=pid2026;
 h=from:to:subject:date:message-id:list-unsubscribe:list-unsubscribe-post; ...

O que o cliente de email envia

Quando o usuário clica em "Cancelar inscrição" na interface do Gmail, por exemplo, o provedor faz algo assim:

HTTP
POST /descadastro/eyJ1IjoiOWYyYyJ9.c2ln HTTP/1.1
Host: example.com.br
Content-Type: application/x-www-form-urlencoded
Content-Length: 26

List-Unsubscribe=One-Click

Repare: sem cookies, sem sessão, sem redirecionamento para tela de confirmação. A RFC pede que a URL contenha todas as informações necessárias para identificar o destinatário e a lista, e que o POST não dependa de nada além disso.

Implementação passo a passo

1. Gere um token opaco e assinado

A URL precisa identificar quem e de qual lista descadastrar, sem expor o email em texto puro e sem permitir que alguém descadastre outra pessoa trocando um ID. Uma forma simples é um payload assinado com HMAC:

Python
import hmac, hashlib, base64, json

SEGREDO = b"troque-por-um-segredo-forte"

def gerar_token(contato_id: str, stream: str) -> str:
    payload = base64.urlsafe_b64encode(
        json.dumps({"c": contato_id, "s": stream}).encode()
    ).rstrip(b"=")
    assinatura = base64.urlsafe_b64encode(
        hmac.new(SEGREDO, payload, hashlib.sha256).digest()
    ).rstrip(b"=")
    return f"{payload.decode()}.{assinatura.decode()}"

Na verificação, use comparação em tempo constante (hmac.compare_digest).

2. Separe POST de GET

  • POST com corpo List-Unsubscribe=One-Click: descadastra imediatamente e responde 200 (ou 202). Nada de HTML de confirmação.
  • GET: mostra uma página com um botão "Confirmar descadastro" que, por sua vez, faz um POST. Nunca descadastre em GET.
Python
@app.post("/descadastro/<token>")
def descadastro_post(token):
    dados = validar_token(token)          # 400 se inválido
    suprimir(dados["c"], stream=dados["s"], origem="one-click")
    return "", 200

@app.get("/descadastro/<token>")
def descadastro_get(token):
    validar_token(token)
    return render("confirmar_descadastro.html", token=token)

3. Torne a operação idempotente

O mesmo POST pode chegar várias vezes (retentativas do provedor, clique duplo). Descadastrar alguém que já está descadastrado deve retornar sucesso, não erro.

4. Defina o escopo

Descadastrar da newsletter não deveria bloquear o envio de nota fiscal ou do código de login. Inclua o stream ou a categoria no token e aplique a supressão apenas nele. Isso também ajuda a cumprir a LGPD, que exige respeitar a revogação do consentimento para a finalidade específica.

O header não substitui o link no rodapé. Usuários que não veem o botão do cliente de email precisam de outra forma de sair, e as diretrizes do Google e do Yahoo continuam pedindo um link claro na mensagem.

Gateways de segurança corporativos, antivírus e pré-visualizadores de URL abrem os links das mensagens para verificar se são maliciosos. Se o seu link de descadastro funciona com GET, esses robôs descadastram pessoas que nunca pediram. Sintomas típicos:

  • Descadastros que acontecem segundos depois da entrega.
  • Picos de descadastro concentrados em domínios corporativos.
  • Vários links da mesma mensagem acessados em sequência, do mesmo IP.

A defesa é a da seção anterior: efeito colateral só em POST. Scanners em geral fazem GET; o POST de um clique vem do provedor de email, por ação do usuário. Se você precisar de um link no corpo que funcione sem JavaScript, faça o GET exibir um formulário com botão.

Outras armadilhas

  • Redirecionar o POST para outra URL. Alguns clientes não seguem redirecionamentos em POST; responda diretamente.
  • Exigir login para descadastrar. Viola a RFC e gera reclamação de spam, que custa muito mais caro.
  • Responder com erro em token expirado. Mensagens antigas continuam na caixa do usuário. Tokens de descadastro não devem expirar, ou devem durar muito tempo.
  • Header quebrado. URLs longas sem dobra correta (folding) ou sem < > fazem o cliente ignorar o header.
  • DKIM sem os headers no h=. O botão pode não aparecer ou o provedor pode ignorar o um clique.
  • Processar em lote uma vez por semana. O prazo é de dois dias; o ideal é na hora.

Como testar

  1. Envie para uma conta do Gmail e verifique se o link "Cancelar inscrição" aparece ao lado do remetente (ele não aparece para todos os remetentes; depende também de reputação).
  2. Simule o POST:
bash
curl -i -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data "List-Unsubscribe=One-Click" \
  "https://example.com.br/descadastro/eyJ1IjoiOWYyYyJ9.c2ln"
  1. Faça um GET na mesma URL e confirme que o contato continua inscrito.
  2. Repita o POST e confirme que a resposta continua 200.

Checklist

  • [ ] List-Unsubscribe com URL HTTPS (e mailto: opcional).
  • [ ] List-Unsubscribe-Post: List-Unsubscribe=One-Click.
  • [ ] Os dois headers no h= da assinatura DKIM.
  • [ ] Token assinado, sem email em texto puro, sem expiração curta.
  • [ ] POST descadastra na hora, sem redirecionamento, de forma idempotente.
  • [ ] GET nunca descadastra.
  • [ ] Supressão por stream ou categoria.
  • [ ] Link visível no corpo da mensagem.

Na Pidgely

A Pidgely adiciona os headers da RFC 8058 nos envios comerciais, assina com DKIM cobrindo esses headers, hospeda o endpoint de POST e registra o descadastro na lista de supressão do stream certo, disparando um webhook para a sua aplicação sincronizar o status do contato.

Perguntas frequentes

O que é o descadastro de um clique da RFC 8058?

É um mecanismo em que o cliente de email envia um POST HTTPS para a URL do header List-Unsubscribe, com o corpo List-Unsubscribe=One-Click, e o remetente descadastra o usuário sem pedir confirmação nem login.

Só o header List-Unsubscribe com mailto basta?

Não para o requisito de um clique do Gmail e do Yahoo. É preciso uma URL HTTPS no List-Unsubscribe e o header List-Unsubscribe-Post: List-Unsubscribe=One-Click, ambos cobertos pela assinatura DKIM.

Por que o descadastro deve aceitar só POST?

Porque scanners de segurança e pré-visualizadores de link fazem GET em URLs das mensagens. Se um GET descadastrar, usuários serão removidos sem ter pedido.

Em quanto tempo preciso processar o descadastro?

Google e Yahoo pedem que o pedido seja honrado em até dois dias. O ideal é descadastrar na hora, de forma síncrona, no próprio endpoint.

Mensagens transacionais precisam desses headers?

A exigência vale para mensagens comerciais e promocionais. Confirmação de pedido ou redefinição de senha não precisam, e geralmente não devem ter o descadastro do marketing.

Time Pidgely

Publicado em

Construímos o Pidgely, a API de email que leva você à caixa de entrada. Escrevemos sobre o que aprendemos entregando emails, citando os padrões e as diretrizes dos provedores por trás de cada afirmação.

Também disponível em: