Skip to content

planetaryescape/list-unsubscribe

Repository files navigation

list-unsubscribe

Crates.io Documentation License

Parse List-Unsubscribe (RFC 2369) and List-Unsubscribe-Post (RFC 8058) email headers into a typed action enum.

Install

[dependencies]
list-unsubscribe = "0.1"
use list_unsubscribe::{parse_with_post, UnsubscribeMethod};

let header = "<mailto:u@example.com>, <https://example.com/unsub?u=abc>";
let post = Some("List-Unsubscribe=One-Click");

match parse_with_post(header, post) {
    UnsubscribeMethod::OneClick { url } => {
        // POST to `url` with body `List-Unsubscribe=One-Click`
    }
    UnsubscribeMethod::Mailto { address, subject } => {
        // Open mail composer to `address` with `subject`
    }
    UnsubscribeMethod::HttpLink { url } => {
        // Open `url` in a browser
    }
    UnsubscribeMethod::None => {
        // No header offered, or every candidate was unparseable
    }
}

Why this crate exists

In February 2024 Gmail and Yahoo introduced bulk-sender deliverability requirements. One of them is mandatory RFC 8058 one-click unsubscribe for senders above 5,000 messages/day. This promoted List-Unsubscribe-Post from "obscure RFC" to "required for inbox placement", and elevated the audience for clients that honour it.

List-Unsubscribe describes an action, not just a header value. Callers still need to choose between mailto, web-link, and RFC 8058 one-click actions, while handling mailto: query parameters consistently.

This crate keeps that policy surface small: parse the headers into a typed action enum, then leave execution to the caller.

What it does

  • Parses RFC 2369 multi-method headers like <mailto:list@x>, <https://x/u>.
  • Distinguishes RFC 8058 one-click (POST endpoint) from a plain web link via the accompanying List-Unsubscribe-Post header.
  • Captures the ?subject= parameter from mailto: URIs.
  • Skips unparseable URIs silently and falls through to the next candidate.

What it does not do

  • It does not POST to the one-click endpoint. The caller picks an HTTP client such as reqwest or ureq and executes the action.
  • It does not send the unsubscribe mail. The caller hands the Mailto variant to a mail composer.
  • It does not scrape unsubscribe links from the message body. That is a policy decision that belongs above the crate.
  • It does not capture ?body= from mailto: URIs. See "Intentional divergences" below.
  • It does not verify the unsubscribe endpoint actually works. The contract is "parse the header, classify the method".

Spec anchors

Conformance

The full coverage matrix lives in testdata/coverage.md. Each fixture is a language-neutral JSON file under testdata/conformance/ so another implementation can load the same corpus.

Three tests enforce the integrity of the corpus:

  1. Every fixture file is referenced in coverage.md.
  2. Every contract-critical fixture exists on disk.
  3. The actual parser output matches expected for every fixture.

Run them with:

cargo test --all-features

Intentional divergences

These are decisions where this crate is narrower or more opinionated than the spec.

  • Mailto preferred over http when both are present and no one-click Post header. Mailto unsubscribe does not require a browser session and tends to be faster for power users; clients that want the opposite preference can pattern-match on the returned enum.
  • ?body= dropped from mailto: URIs. Including it would let clients prepare message text on the user's behalf. This crate returns only the address and optional subject.
  • Multiple URLs of the same scheme: first wins. RFC 2369 does not specify ordering; this gives callers a deterministic single choice.

Feature flags

  • serde — derives Serialize + Deserialize for UnsubscribeMethod (internally tagged with kind), and pulls in url/serde.
  • mail-parser — adds parse_from_message(&mail_parser::Message<'_>) for callers that already use the mail-parser crate to parse RFC 5322 messages.

The default feature set is empty. The crate has one required dependency (url).

Out of Scope

Execution helpers are intentionally outside this crate's current surface. Callers choose the HTTP client for one-click unsubscribe and the mail composer or SMTP client for mailto: actions.

Request executor support with a concrete client or runtime use case.

Maintenance

License

MIT OR Apache-2.0. See LICENSE-MIT and LICENSE-APACHE.

About

Parse RFC 2369 List-Unsubscribe + RFC 8058 one-click headers into a typed action enum.

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages