Przejdź do treści
correct.email
Dostarczalność

Wypisanie jednym kliknięciem (RFC 8058) dla programistów

Jak wdrożyć List-Unsubscribe i List-Unsubscribe-Post zgodnie z RFC 8058: endpoint HTTPS POST, podpis DKIM, zapasowy mailto i testowanie.

Autor: Exmoor Software6 min czytania

Gdy wiadomość marketingowa ma odpowiednie nagłówki, dostawcy skrzynek tacy jak Gmail mogą wyświetlić obok nazwy nadawcy przycisk wypisania z subskrypcji. Gdy odbiorca go użyje, to serwery dostawcy — a nie przeglądarka odbiorcy — wysyłają do Twojego serwera żądanie HTTPS POST, a Ty usuwasz adres z listy. Bez strony docelowej, bez logowania, bez „czy na pewno?”.

Mechanizm opisuje RFC 8058, który rozszerza starszy nagłówek List-Unsubscribe z RFC 2369. Gmail i Yahoo wymagają go od nadawców masowych w wiadomościach marketingowych i subskrypcyjnych (zobacz wymagania Gmaila, Yahoo i Outlook.com). Ten poradnik opisuje, co trzeba zbudować.

Dlaczego POST, a nie zwykły link

List-Unsubscribe mógł zawierać linki HTTPS na długo przed RFC 8058. Problem, jak wyjaśnia RFC, polega na tym, że oprogramowanie antyspamowe często samo pobiera adresy URL z nagłówków, a Twój serwer nie odróżni takiego automatycznego żądania od kliknięcia człowieka. Nadawcy bronili się stronami z potwierdzeniem, których automat nie kliknie — i przez które wypisanie wymaga więcej niż jednego kliknięcia.

RFC 8058 dodaje osobny sygnał: żądanie POST ze stałą treścią. Skaner, który pobiera adres URL, wysyła GET; dostawca skrzynki działający na prośbę użytkownika wysyła POST. Twój serwer może więc traktować POST jako jednoznaczną decyzję o wypisaniu.

Dwa nagłówki

List-Unsubscribe: <https://example.com/unsubscribe/7f3c9a1e2b>, <mailto:unsubscribe@example.com?subject=unsubscribe-7f3c9a1e2b>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

Zasady z RFC 8058, sekcja 3.1:

  • Wiadomość zawiera jeden nagłówek List-Unsubscribe i jeden nagłówek List-Unsubscribe-Post.
  • List-Unsubscribe musi zawierać jeden adres URI HTTPS. Może zawierać również adresy innego typu niż HTTP, na przykład mailto:.
  • List-Unsubscribe-Post musi zawierać dokładnie parę List-Unsubscribe=One-Click.
  • Adres URI musi zawierać dość informacji, by zidentyfikować odbiorcę i listę. Żądanie POST nie ma innych parametrów i nie ma kogo o nic zapytać.
  • Adres URI powinien zawierać nieprzejrzysty, trudny do podrobienia element, a Twój serwer powinien go weryfikować. Inaczej ktokolwiek mógłby wypisywać Twoich subskrybentów, wysyłając żądania na odgadnięte adresy, albo wstawiać Twoje adresy do spamu, żeby wywołać wypisania.

Obowiązuje składnia z RFC 2369: każdy adres w nawiasach ostrych, kilka adresów rozdzielonych przecinkami, w kolejności preferencji od lewej do prawej.

Jako token użyj losowego identyfikatora zapisanego przy subskrypcji albo HMAC z identyfikatorów subskrybenta i listy. Nie umieszczaj w adresie URL jawnego adresu e-mail ani identyfikatora numerowanego po kolei.

Co wysyła dostawca skrzynki

Żądanie trafia na adres HTTPS z nagłówka, a jego treścią jest para klucz–wartość. To przykład z RFC 8058, sekcja 8.1, z naszym tokenem:

POST /unsubscribe/7f3c9a1e2b HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 26

List-Unsubscribe=One-Click
  • Treść powinna być wysłana jako multipart/form-data, ale może też przyjść jako application/x-www-form-urlencoded. Obsłuż oba formaty.
  • Żądanie nie może zawierać ciasteczek, uwierzytelnienia HTTP ani żadnego innego kontekstu. Twój endpoint nie może polegać na sesji.
  • System odbiorcy nie może wysłać żądania POST bez zgody użytkownika; to, jak ją uzyskuje, zależy od dostawcy.

Budowa endpointu

// POST https://example.com/unsubscribe/<token>
export async function handleOneClick(token: string): Promise<Response> {
  const sub = await findSubscriptionByToken(token); // tylko tokeny wydane przez Ciebie
  if (!sub) return new Response(null, { status: 404 });
  await suppress(sub.subscriberId, sub.listId);     // idempotentnie: już wypisany = sukces
  return new Response(null, { status: 200 });       // bez przekierowania i potwierdzenia
}

Zasady, które stoją za tymi kilkoma liniami:

  1. Samo żądanie POST wypisuje. Bez kroku potwierdzenia. RFC 8058 powstał z myślą o sytuacjach, w których nikt nie odwiedza Twojej strony — na przykład gdy dostawca łączy „zgłoś spam” z wypisaniem.
  2. Bez przekierowań. Nadawca nie może odpowiadać przekierowaniem HTTPS, bo przekierowane żądania POST od dawna działają zawodnie. Adres w nagłówku musi być docelowy: nie link śledzący kliknięcia, nie adres http:// przekierowujący na https://, nie ścieżka, którą framework przekierowuje, żeby dodać ukośnik na końcu.
  3. Bez tokenu CSRF i bez logowania. Żądanie przychodzi z serwerów dostawcy, bez ciasteczek. Jeśli Twój framework wymusza ochronę CSRF dla żądań POST, wyłącz ją dla tej ścieżki — tę rolę pełni trudny do podrobienia token w adresie URL.
  4. Zadbaj o idempotentność. To samo żądanie może przyjść więcej niż raz: użytkownik kliknie dwa razy, żądanie zostanie ponowione. Adres, który jest już wypisany, traktuj jako sukces. Powtórzone żądanie nigdy nie może skończyć się błędem ani ponownym zapisem.
  5. Wypisuj z listy wskazanej w adresie. Google zaznacza, że wypisanie jednym kliknięciem usuwa odbiorcę tylko z listy powiązanej z daną wiadomością, więc osobne listy mogą pozostać osobne.
  6. Działaj szybko. Yahoo wymaga uwzględnienia wypisania w ciągu 2 dni, Google zaleca 48 godzin. Usuń adres także z wysyłek w kolejce i zaplanowanych, a nie dopiero przy następnym imporcie.
  7. Zwróć status sukcesu. RFC 8058 nie określa odpowiedzi i nikt jej nie czyta; 200 albo 204 wystarczy.

Ten sam adres może obsługiwać ludzi, którzy otworzą go w przeglądarce. RFC 8058 zauważa, że cel żądania POST jest taki sam jak przy ręcznym wypisaniu, więc jedna procedura może obsłużyć oba przypadki. Na żądanie GET pokaż stronę z przyciskiem wypisania (formularz wysyłający POST na ten sam adres). Nie wypisuj na samo GET — właśnie takie żądania wysyłają skanery linków.

DKIM musi obejmować oba nagłówki

RFC 8058, sekcja 4: wiadomość potrzebuje co najmniej jednego poprawnego podpisu DKIM, a nagłówki List-Unsubscribe i List-Unsubscribe-Post muszą być wymienione w tagu h= tego podpisu. Bez tego system odbiorcy nie powinien oferować wypisania jednym kliknięciem dla tej wiadomości.

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=s2026;
        h=from:to:subject:date:message-id:list-unsubscribe:list-unsubscribe-post;
        bh=...; b=...

Typowy błąd to kolejność: nagłówki są dodawane po podpisaniu — przez serwer pośredniczący, filtr na Twoim serwerze poczty albo drugą usługę — więc są w wiadomości, ale podpis ich nie obejmuje. Dodawaj je w aplikacji albo w wywołaniu API usługi wysyłkowej, zanim wiadomość zostanie podpisana. Na potrzeby wymagań Gmaila i Yahoo podpis powinien też być zgodny z domeną z nagłówka From. Więcej o podpisywaniu w poradniku Jak działa DKIM.

Zapasowy adres mailto

RFC 8058 dopuszcza w List-Unsubscribe dodatkowe adresy innego typu niż HTTP, a mailto: to pierwotna forma z RFC 2369. Niektóre programy pocztowe wciąż z niej korzystają.

  • Gmail obsługuje mailto:, ale nie uznaje go za wypisanie jednym kliknięciem — potrzebny jest adres HTTPS.
  • Yahoo zdecydowanie zaleca metodę POST i akceptuje mailto:.

Jeśli dodajesz mailto:, przetwarzaj takie wiadomości automatycznie i zakoduj w nich ten sam token — w temacie, jak w przykładzie, albo w adresie (np. unsubscribe+7f3c9a1e2b@example.com, jeśli Twój system poczty obsługuje adresy z plusem). Adres do wypisań, który trafia do nieczytanej skrzynki, jest gorszy niż żaden.

Które wiadomości tego wymagają

Wiadomości marketingowe i subskrypcyjne. Google i Yahoo wyłączają z wymogu wiadomości transakcyjne, takie jak reset hasła czy potwierdzenie zamówienia. Obaj dostawcy oczekują też widocznego linku do wypisania w treści; może on prowadzić do strony z ustawieniami subskrypcji i nie musi działać jednym kliknięciem. O poczcie wysyłanej z własnego kodu przeczytasz w poradniku o poczcie transakcyjnej z aplikacji.

Testowanie

  1. Sprawdź nagłówki. Wyślij wiadomość z kampanii do swojej skrzynki, otwórz oryginał (w Gmailu: ⋮ → „Pokaż oryginał”) i wklej nagłówki do analizatora nagłówków e-mail. Panel Wypisanie pokazuje adresy i informację „jednym kliknięciem (RFC 8058)”, gdy List-Unsubscribe-Post jest poprawny. W sekcji Podpisy DKIM lista Podpisane nagłówki musi zawierać list-unsubscribe i list-unsubscribe-post, a wyniki uwierzytelniania muszą pokazywać dkim=pass.

  2. Zasymuluj żądanie dostawcy w obu formatach:

    curl -i -X POST -H 'Content-Type: application/x-www-form-urlencoded' \
      --data 'List-Unsubscribe=One-Click' 'https://example.com/unsubscribe/7f3c9a1e2b'
    
    curl -i -F 'List-Unsubscribe=One-Click' 'https://example.com/unsubscribe/7f3c9a1e2b'
    

    Oczekuj statusu 2xx i braku nagłówka Location. Wyślij żądanie jeszcze raz — nadal 2xx. Zmieniony token powinien zostać odrzucony.

  3. Sprawdź efekt. Adres jest wypisany z tej listy i pominięty w następnej wysyłce.

  4. Otwórz adres w przeglądarce. Powinna pojawić się strona z przyciskiem, a nic nie powinno się zmienić, dopóki go nie naciśniesz.

Nie traktuj przycisku w Gmailu jako testu. Google wyświetla go tylko przy wiadomościach, które przechodzą automatyczne kontrole kwalifikujące i spełniają jego wymagania dla nadawców, więc brak przycisku nie musi oznaczać błędu w nagłówkach.

Najczęstsze błędy

  • Adres w nagłówku prowadzi przez przekierowanie śledzące kliknięcia.
  • Endpoint odpowiada na POST kodem 405, bo zaimplementowano tylko GET.
  • Ochrona CSRF odrzuca żądanie, często z kodem 403.
  • Nagłówki są dodawane po podpisaniu DKIM.
  • Adres URL zawiera jawny adres e-mail, więc każdy może wypisać każdego.
  • Tylko adres mailto: — niektóre programy z niego skorzystają, ale nie spełnia wymogu Gmaila.
  • Wypisania są uwzględniane dopiero przy cotygodniowym imporcie.

Źródła