{"id":50965609,"url":"https://github.com/tricked-dev/email-privacy-cleaner","last_synced_at":"2026-06-18T20:01:38.636Z","repository":{"id":361682253,"uuid":"1255228282","full_name":"Tricked-dev/email-privacy-cleaner","owner":"Tricked-dev","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-31T19:05:06.000Z","size":114,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-31T21:07:36.016Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Tricked-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"Tricked-dev"}},"created_at":"2026-05-31T15:13:00.000Z","updated_at":"2026-05-31T19:05:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Tricked-dev/email-privacy-cleaner","commit_stats":null,"previous_names":["tricked-dev/email-privacy-cleaner"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/Tricked-dev/email-privacy-cleaner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tricked-dev%2Femail-privacy-cleaner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tricked-dev%2Femail-privacy-cleaner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tricked-dev%2Femail-privacy-cleaner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tricked-dev%2Femail-privacy-cleaner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Tricked-dev","download_url":"https://codeload.github.com/Tricked-dev/email-privacy-cleaner/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tricked-dev%2Femail-privacy-cleaner/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34505423,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-18T20:01:35.548Z","updated_at":"2026-06-18T20:01:38.623Z","avatar_url":"https://github.com/Tricked-dev.png","language":"Rust","funding_links":["https://github.com/sponsors/Tricked-dev"],"categories":[],"sub_categories":[],"readme":"# email-privacy-cleaner\n\nA reusable **Rust library** plus a **milter daemon** that sanitise the\nprivacy-invasive parts of email, designed to run as a pre-queue milter for the\n[Stalwart](https://stalw.art) mail server at the SMTP **DATA** stage (but usable\nstandalone via the CLI).\n\nIt performs these independent, deterministic, **offline** transformations:\n\n1. **Tracking-pixel removal** — drops 1×1 / hidden / known-beacon `\u003cimg\u003e` tags,\n   and neutralises beacons hidden in CSS (`background-image: url(…)` in an\n   inline `style`, the legacy `background=\"…\"` attribute) when the URL is a\n   known beacon host or is fetched by a hidden / 1×1 element.\n2. **Hyperlink-auditing removal** — strips the `ping` attribute from `\u003ca\u003e`/\n   `\u003carea\u003e`, which would otherwise fire a hidden POST beacon on every click.\n3. **Tracking query-parameter stripping** — `utm_*`, `fbclid`, `gclid`,\n   `mc_cid`, `_hsenc`, … (configurable, case-insensitive; `prefix*` supported).\n4. **Vendor-specific URL cleaning** — host-scoped rules for Amazon (`pf_rd_*`,\n   `tag`, …), YouTube (`si`), eBay (`_trkparms`), Twitter/X, LinkedIn, Reddit,\n   TikTok and more, shipped in the built-in rule pack.\n5. **First-stage ESP redirect unwrapping** — SendGrid, Mailchimp, Mandrill,\n   Constant Contact, HubSpot, Customer.io, Iterable, Klaviyo, Mailgun,\n   Brevo/Sendinblue, Postmark, SparkPost — **only** when the destination is\n   explicitly embedded in the link and passes validation.\n\nIt is also **sender-aware**:\n\n* **Per-sender policies** — config rules keyed by the `From:` domain can switch\n  mode or toggle individual features per sender.\n* **Sensitive-sender protection** — for built-in identity/payment/auth senders,\n  link rewriting and redirect unwrapping are skipped so magic-login / 2FA /\n  password-reset links never break (pixel removal still applies).\n* **List-Unsubscribe handling** — the unsubscribe link is treated as sensitive\n  and left byte-for-byte intact (its recipient token survives); its target can\n  be surfaced in an `X-Privacy-Cleaner-Unsubscribe` header.\n\nA network redirect resolver and HTTP(S) rule-pack fetching are compiled in by\ndefault (the `network` cargo feature), but both stay **inert unless you opt in**\nat runtime, and the resolver is heavily SSRF-guarded. See\n[the note on the network feature](#note-on-the-network-feature) to build them\nout entirely.\n\n\u003e No network access during cleaning. No JavaScript execution. No image\n\u003e fetching. Attachments are never altered. The per-message cleaning path is\n\u003e deterministic and bounded — suitable for the synchronous SMTP DATA stage.\n\n## Crate layout\n\n```\nemail_privacy_cleaner          (library crate)\n├── config        CleanerConfig (TOML), sender policies, rule-pack loading\n├── ruleset       ClearURLs-format rule engine (built-in + external packs)\n├── url_clean     clean_url()              — query-param + vendor stripping\n├── redirect      unwrap_redirect_url()    — offline ESP unwrapping\n├── validate      destination validation + SSRF IP blocking\n├── html          clean_html()             — lol_html-based rewriting\n├── encoding      QP / base64 / charset re-encoding\n├── mime          clean_message()          — byte-surgical MIME rewriting\n├── network       optional resolver (feature = \"network\", off by default)\n└── milter        Sendmail/Postfix milter-protocol server\n\nbinaries:\n  email-privacy-cleaner   CLI (clean / explain / diff / print-trackers)\n  email-privacy-milter    milter daemon\n```\n\n### Public API\n\n```rust\nuse email_privacy_cleaner::{\n    clean_message, clean_html, clean_url, unwrap_redirect_url, CleanerConfig,\n};\n\nlet cfg = CleanerConfig::default();\nlet result = clean_message(raw_eml_bytes, \u0026cfg)?;\n// result.cleaned        -\u003e full message incl. audit headers\n// result.body           -\u003e body region only (used by the milter for REPLBODY)\n// result.audit_headers  -\u003e Vec\u003c(name, value)\u003e\n// result.stats          -\u003e CleanStats { html_parts, urls_cleaned, .. }\n```\n\n## How body rewriting preserves the message\n\n`mail-parser` records the byte offsets of every MIME part. We re-encode **only**\nthe `text/html` (and optionally `text/plain`) body parts and splice the new\nbytes back into the original message in place — headers, MIME boundaries,\nattachments and nested parts are preserved verbatim. Modified parts are\nre-encoded using the part's original `Content-Transfer-Encoding`\n(7bit / quoted-printable / base64) and declared charset.\n\nBecause the body is modified, an `X-Privacy-Cleaner-Body-Modified: yes` header\nis added so downstream DKIM expectations are explicit (the body hash will change\nwhen the cleaner is positioned before signing, or signatures should be applied\nafter cleaning).\n\n## Audit headers\n\n```\nX-Privacy-Cleaner: email-privacy-cleaner/\u003cversion\u003e\nX-Privacy-Cleaner-Mode: enforce | report-only\nX-Privacy-Cleaner-HTML-Parts: \u003cn\u003e\nX-Privacy-Cleaner-URLs-Cleaned: \u003cn\u003e\nX-Privacy-Cleaner-Redirects-Unwrapped: \u003cn\u003e\nX-Privacy-Cleaner-Pixels-Removed: \u003cn\u003e          # incl. CSS background beacons\nX-Privacy-Cleaner-Link-Pings-Stripped: \u003cn\u003e\nX-Privacy-Cleaner-Body-Modified: yes | no\nX-Privacy-Cleaner-Policy: default | sensitive-sender | custom:\u003cdomain\u003e\nX-Privacy-Cleaner-Unsubscribe: \u003curl\u003e       # when surface_unsubscribe + present\nX-Privacy-Cleaner-Error: \u003cshort error\u003e     # only on fail-open\n```\n\n## Build\n\n```bash\ncargo build --release\n# binaries in target/release/{email-privacy-cleaner,email-privacy-milter}\n# the `network` feature is on by default (see the note at the bottom)\n\n# fully offline build (no networking code compiled in):\ncargo build --release --no-default-features\n```\n\n## CLI usage\n\n```bash\n# Clean a full message\nemail-privacy-cleaner clean-message --config config.toml \u003c raw.eml \u003e cleaned.eml\n\n# Clean an HTML fragment\nemail-privacy-cleaner clean-html --config config.toml \u003c input.html \u003e output.html\n\n# Explain how one URL is treated (provider, unwrap, params)\nemail-privacy-cleaner explain-url \"https://u1.ct.sendgrid.net/ls/click?url=https%3A%2F%2Fexample.com%2Fp%3Futm_source%3Dx\"\n\n# Explain a whole message: sender policy, per-link treatment, unsubscribe target\nemail-privacy-cleaner explain-message --config config.toml \u003c raw.eml\n\n# List the trackers detected in a message (params, ESP wrappers, pixels)\nemail-privacy-cleaner print-trackers \u003c raw.eml\n\n# Show a line diff between the original and cleaned message\nemail-privacy-cleaner diff-message \u003c raw.eml\n\n# Run the cleaner over a directory of *.eml fixtures and report\nemail-privacy-cleaner test-rules tests/fixtures/\n```\n\n`explain-message` example output:\n\n```\nsender:   news.example.com\npolicy:   default\neffective: clean_query_params=true unwrap_redirects=true vendor_rules=true remove_pixels=true mode=enforce\nunsubscribe: https://news.example.com/u?uid=42\u0026tok=SECRET, mailto:unsub@news.example.com\nhtml-parts: 1\n  [1] https://shop.example.com/sale?id=1\u0026utm_source=news\n      -\u003e CLEAN (stripped [\"utm_source\"]) -\u003e https://shop.example.com/sale?id=1\n  [2] https://news.example.com/u?uid=42\u0026tok=SECRET\n      -\u003e SENSITIVE (List-Unsubscribe) — left untouched\n```\n\n`explain-url` example output:\n\n```\ninput:    https://u1.ct.sendgrid.net/ls/click?url=https%3A%2F%2Fexample.com%2Fp%3Futm_source%3Dx\nprovider: sendgrid\nunwrapped: yes -\u003e https://example.com/p\nquery-clean: no tracking params\nfinal:    https://example.com/p\n```\n\n## Running the milter\n\n```bash\nemail-privacy-milter --config config.toml\n# or override the listen address:\nemail-privacy-milter --listen 127.0.0.1:11333\n```\n\nThe daemon speaks the standard Sendmail/Postfix milter protocol (version 6),\nnegotiates the *add headers* and *replace body* actions, accumulates the\nmessage at the DATA stage, runs the cleaner, then emits `SMFIR_REPLBODY` +\n`SMFIR_ADDHEADER` modifications.\n\n### Stalwart integration\n\nStalwart can call out to a milter at the DATA stage. Point it at the daemon's\nlisten address (TOML config sketch):\n\n```toml\n[session.data.milter.\"privacy\"]\nenable = true\nhostname = \"127.0.0.1\"\nport = 11333\noptions.version = 6\noptions.tls = false\n```\n\n(Consult the Stalwart docs for the exact key names in your version; the milter\nitself requires no Stalwart-specific behaviour beyond standard milter v6.)\n\nRecommended rollout:\n\n1. Start in `mode = \"report-only\"` — headers are added, the body is untouched.\n2. Inspect `X-Privacy-Cleaner-*` headers on delivered mail.\n3. Switch to `mode = \"enforce\"` once satisfied.\n\n### Failure behaviour\n\n* `fail_open = true` (default): on an internal parser error the **original**\n  message is passed through unchanged with an `X-Privacy-Cleaner-Error` header.\n* `fail_open = false`: the milter returns a **tempfail** so the MTA retries.\n\n## Configuration\n\nSee [`config.example.toml`](config.example.toml) for every option with its\ndefault. Highlights:\n\n| Key | Default | Meaning |\n|-----|---------|---------|\n| `mode` | `enforce` | `enforce` or `report-only` |\n| `clean_html` | `true` | rewrite text/html parts |\n| `clean_text_plain` | `false` | query-clean text/plain parts |\n| `remove_pixels` | `true` | drop tracking pixels |\n| `neutralize_css_beacons` | `true` | neutralise CSS `background-image` / `background=` beacons (needs `remove_pixels`) |\n| `strip_link_ping` | `true` | strip the hyperlink-auditing `ping` attribute |\n| `clean_query_params` | `true` | strip tracking params |\n| `apply_vendor_rules` | `true` | host-scoped (non-global) rule-pack rules |\n| `strip_referral_marketing` | `false` | also strip `referralMarketing` params |\n| `unwrap_known_redirects` | `true` | offline ESP unwrapping |\n| `protect_sensitive_senders` | `true` | skip link rewriting for auth/payment senders |\n| `surface_unsubscribe` | `true` | add `X-Privacy-Cleaner-Unsubscribe` header |\n| `sender_policies` | `[]` | per-sender overrides (first match wins) |\n| `network_redirect_resolution` | `false` | opt-in network resolver |\n| `preserve_original_href` | `false` | keep original in `data-original-href` (off by default — the attribute lives in the HTML body and gets carried into recipients' replies/forwards) |\n| `fail_open` | `true` | pass-through vs tempfail on error |\n| `max_message_size` | 50 MiB | hard input limit |\n| `max_html_part_size` | 8 MiB | per-part HTML limit |\n| `blocked_domains` | `[]` | links neutralised to `about:blank` |\n| `extra_tracking_params` | `[]` | merged with built-ins (`prefix*` allowed) |\n| `extra_pixel_domains` | `[]` | merged with built-ins |\n| `keep_params` | `[]` | params never stripped, even if a rule matches (`prefix*`) |\n| `exclude_domains` | `[]` | hosts left entirely untouched (suffix match) |\n| `disabled_providers` | `[]` | rule-pack provider names to switch off |\n| `rule_packs` | `[]` | external ClearURLs-format pack files to merge |\n| `rule_pack_urls` | `[]` | pack URLs (`file://`/paths load offline; `http(s)` needs `network`) |\n\n## Rule packs (ClearURLs format)\n\nThe default tracking params, vendor rules, ESP redirect unwrappers and known\nbeacon hosts ship as a **ClearURLs-format JSON document** compiled into the\nbinary ([`rules/builtin.json`](rules/builtin.json)) — original work under this\ncrate's MIT/Apache licence. The same parser loads **external packs** (file paths\nvia `rule_packs`, or URLs via `rule_pack_urls` with the `network` feature) and\n**merges** them on top of the built-ins, so coverage can be extended without\ncode changes — including with the upstream [ClearURLs](https://clearurls.xyz)\n`data.min.json`.\n\nSupported per provider: `urlPattern`, `rules` (param-name regexes),\n`referralMarketing`, `rawRules`, `exceptions`, `redirections` (capture group 1\nis the embedded destination, always re-validated by `validate` before any link\nis rewritten), and `completeProvider` (treated as a beacon-host signal for\ntracking-pixel detection only — **never** used to neutralise an `\u003ca\u003e` link, so\nclick-throughs are never broken). Regexes compile with the linear-time `regex`\ncrate (no catastrophic backtracking); patterns using unsupported features (e.g.\nlookaround in a third-party pack) are skipped individually while the rest of the\npack still loads.\n\n\u003e The upstream ClearURLs rules are copyleft (LGPL); this crate ships only the\n\u003e parser, not that data. Download it and point `rule_packs`/`rule_pack_urls` at\n\u003e it if you want it.\n\n## Redirect unwrapping\n\nEmail platforms often replace normal links with a click-tracking URL. This\nproject handles those redirects in two layers:\n\n* **Offline unwrapping** is the default path. It works when the redirect URL\n  already contains the real destination in a query parameter or path fragment.\n  Common examples are SendGrid-style links with a `url=...` value and similar\n  ESP wrappers. The destination is decoded, validated, cleaned again for\n  tracking parameters, and then written back into the message. No HTTP request\n  is made.\n* **Network redirect resolution** is for wrappers that do **not** expose the\n  destination in the URL itself. Mailchimp `list-manage.com/track/click` links\n  are the typical example: the only way to discover the final URL is to ask the\n  tracking server for its HTTP `Location` redirect.\n\nNetwork resolution is compiled in by the default `network` cargo feature, but\nit is still off at runtime. To use it, enable it explicitly and list the domains\nthe resolver may contact:\n\n```toml\nnetwork_redirect_resolution = true\nallowlisted_redirect_domains = [\n  \"list-manage.com\",\n  \"links.trusted-esp.com\",\n]\n```\n\nThe allowlist is intentionally strict, but it applies to hosts the resolver is\nallowed to **contact**, not to every possible final website. Each contacted\nredirector hop must match `allowlisted_redirect_domains` by exact host or suffix\nmatch. If an allowlisted redirector returns a `Location` pointing at an\noff-allowlist destination, that destination can be accepted after validation and\nwritten into the message, but it is not fetched. This makes network resolution\nuseful for ESP click links that jump to many different merchant/news sites\nwithout turning the cleaner into a general-purpose web crawler.\n\nDo not add broad entries such as `com` or `net`; that would allow contacting\nfar more hosts than intended. Prefer specific ESP redirector domains you trust\nto receive HEAD requests from the cleaner.\n\nThe resolver uses `HEAD`, follows at most 5 redirects, applies the configured\ntimeout, sends no cookies or auth, executes no JavaScript, and re-checks each\nhop for blocked private, loopback, link-local, metadata, and other internal IP\nranges. If any check fails, the original link is kept, apart from normal query\nparameter cleaning.\n\n## Security model\n\n* **No network by default** — stage-1 unwrapping is purely string/decoding work.\n* **Redirect destinations are validated** before a link is rewritten: http/https\n  only, no userinfo, no control chars, valid host, no suspicious mixed-script\n  (homograph) hostnames, and **literal private/loopback/link-local/metadata IPs\n  are rejected** so a visible link is never pointed at internal infrastructure.\n* **Nested URL-encoding** is decoded up to depth 3; `javascript:`, `file:`,\n  `data:` destinations are always rejected.\n* **Optional network resolver** (when explicitly enabled): contacts only\n  allowlisted redirector hosts, HEAD-first, no cookies/auth/JS, never fetches\n  images, max 5 redirects, per-request timeout, and re-checks contacted hosts\n  against the SSRF blocklist on every hop. Off-allowlist final `Location`\n  targets are validated but not fetched.\n* **Bounded memory/CPU**: size limits on the message and each HTML part; the\n  HTML rewriter (`lol_html`) is streaming.\n\n## Testing\n\n```bash\ncargo test                  # unit + integration + milter-protocol tests\ncargo clippy --all-targets\n```\n\nFixture-based coverage (`tests/fixtures/*.eml` + `tests/integration.rs`) includes:\nmultipart/alternative, Mailchimp / SendGrid / HubSpot links, hidden 1×1 pixels,\nCSS background-image beacons and the legacy `background=` attribute, hyperlink\n`ping` stripping, a legitimate small logo (and visible background) that must\nsurvive, magic login links that must not\nbreak, malformed HTML, quoted-printable and base64 HTML parts, a non-UTF-8\n(ISO-8859-1) charset, nested URL-encoding, malicious `javascript:`/`file:`\nredirects, private-IP redirect destinations, attachment preservation, and a\nfull milter-protocol conversation over a real socket.\n\n## Nix / NixOS\n\nThis repo ships a production-grade flake (`flake.nix` + `nix/`).\n\n### Build \u0026 run\n\n```bash\nnix build                       # ./result/bin/{...} — network resolver enabled\nnix run  .#milter -- --listen 127.0.0.1:11333\nnix run  .#cli    -- explain-url \"https://...\"\n```\n\nThe flake exposes a **single build target with the network resolver compiled\nin**. To get a fully offline binary, override the cargo feature list:\n\n```bash\nnix build --impure --expr \\\n  '(builtins.getFlake (toString ./.)).packages.${builtins.currentSystem}.default.override { cargoFeatures = []; }'\n# or from an overlay / nixpkgs:\n#   pkgs.email-privacy-cleaner.override { cargoFeatures = [ ]; }\n```\n\nBuilds use [crane](https://github.com/ipetkov/crane) with a pinned stable\ntoolchain. Release binaries are stripped, and the source tree and compiler\nwrapper are scrubbed from / asserted absent in the runtime closure\n(`remove-references-to` + `disallowedReferences`). There are **no native\nruntime dependencies** in either case — the network resolver's TLS is\nrustls/ring, not OpenSSL.\n\n### Checks \u0026 dev shell\n\n```bash\nnix flake check     # clippy (-D warnings), rustfmt, full test suite, module build\nnix develop         # dev shell: toolchain + rust-analyzer + cargo-audit/-edit\n```\n\n`direnv allow` will auto-enter the dev shell via `.envrc`.\n\n### NixOS module\n\nAdd the flake and import the module:\n\n```nix\n{\n  inputs.email-privacy-cleaner.url = \"github:tricked-dev/email-privacy-cleaner\";\n\n  outputs = { nixpkgs, email-privacy-cleaner, ... }: {\n    nixosConfigurations.mail = nixpkgs.lib.nixosSystem {\n      modules = [\n        email-privacy-cleaner.nixosModules.default\n        {\n          services.email-privacy-milter = {\n            enable = true;\n            listen = \"127.0.0.1:11333\";\n            settings = {\n              mode = \"report-only\";        # flip to \"enforce\" once verified\n              remove_pixels = true;\n              clean_query_params = true;\n              extra_tracking_params = [ \"my_custom_tracker\" ];\n              # Exclusions (carve-outs that override the rule pack):\n              keep_params = [ \"ref\" ];\n              exclude_domains = [ \"intranet.example\" ];\n              disabled_providers = [ \"amazon\" ];\n            };\n            # Declaratively prefetch external ClearURLs-format packs into the\n            # store (pinned by hash) and load them offline at runtime. Works\n            # alongside `settings` AND `configFile`.\n            rulePacks = [\n              ./my-extra-rules.json\n              {\n                url = \"https://rules2.clearurls.xyz/data.min.json\";\n                sha256 = \"sha256-AAAA...\";   # nix will tell you the real hash\n              }\n            ];\n          };\n        }\n      ];\n    };\n  };\n}\n```\n\nThe module renders `settings` to a TOML config (or accepts a `configFile`) and\nruns the daemon as a hardened, stateless systemd service (`DynamicUser`,\n`ProtectSystem=strict`, locked-down syscall/address-family filters, no\ncapabilities, loopback-only egress when listening on localhost). `rulePacks` are\nprefetched at build time and passed as `--rule-pack` arguments, so they compose\nwith either `settings` or `configFile` and need no runtime network. Point\nStalwart at the configured `listen` address as shown in\n[Stalwart integration](#stalwart-integration).\n\n## Note on the `network` feature\n\nThe `network` cargo feature is **enabled by default**, so a stock\n`cargo build` / `nix build` compiles it in. It only adds *capability*, not\nbehaviour: everything it enables stays inert until you opt in. The per-message\ncleaning path remains fully offline unless `network_redirect_resolution = true`\nis set.\n\nBuilding with `--no-default-features` drops the networking code entirely. The\nonly things unavailable in that build are:\n\n* the optional network **redirect resolver** (`network_redirect_resolution`); and\n* fetching `http(s)://` entries in `rule_pack_urls`.\n\nEverything else is unchanged — including `file://` and local-path rule packs,\nwhich still load offline. (The resolver is also off at runtime by default even\nwhen compiled in, so a default build behaves identically until you flip\n`network_redirect_resolution = true` or add an `http(s)://` rule-pack URL.)\n\n## License\n\nMIT OR Apache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftricked-dev%2Femail-privacy-cleaner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftricked-dev%2Femail-privacy-cleaner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftricked-dev%2Femail-privacy-cleaner/lists"}