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-Unsubscribei jeden nagłówekList-Unsubscribe-Post. List-Unsubscribemusi zawierać jeden adres URI HTTPS. Może zawierać również adresy innego typu niż HTTP, na przykładmailto:.List-Unsubscribe-Postmusi 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ść jakoapplication/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:
- 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.
- 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 nahttps://, nie ścieżka, którą framework przekierowuje, żeby dodać ukośnik na końcu. - 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.
- 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.
- 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.
- 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.
- Zwróć status sukcesu. RFC 8058 nie określa odpowiedzi i nikt jej nie czyta;
200albo204wystarczy.
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
-
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-Postjest poprawny. W sekcji Podpisy DKIM lista Podpisane nagłówki musi zawieraćlist-unsubscribeilist-unsubscribe-post, a wyniki uwierzytelniania muszą pokazywaćdkim=pass. -
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
2xxi braku nagłówkaLocation. Wyślij żądanie jeszcze raz — nadal2xx. Zmieniony token powinien zostać odrzucony. -
Sprawdź efekt. Adres jest wypisany z tej listy i pominięty w następnej wysyłce.
-
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.