···8989- The inbox list shows a `⊙` indicator (orange) for emails that contained tracking pixels, visible after first read.
9090- The reader header shows `⊙ N spy pixel(s) blocked (domain.com, ...)` with the tracker domains.
91919292-**Browser view (`O`) protection:**
9393-- The HTML template includes a `Content-Security-Policy` meta tag that restricts image sources to `file:`, `data:`, and `cid:` only. Remote images (including tracking pixels) are blocked even when viewing the full HTML version in a browser.
9292+**Browser view (`O`):** When you explicitly open an email in the browser, remote images are loaded — this is intentional, as you're choosing to see the full email. Tracking pixels are only blocked in the TUI.
94939595-**Code:** [`internal/imap/client.go`](https://github.com/ssp-data/neomd/blob/main/internal/imap/client.go) — `cleanMarkdown()`, `SpyPixelInfo` · [`internal/render/html.go`](https://github.com/ssp-data/neomd/blob/main/internal/render/html.go) — `htmlTemplate` (CSP) · [`internal/ui/inbox.go`](https://github.com/ssp-data/neomd/blob/main/internal/ui/inbox.go) — `⊙` indicator · [`internal/ui/reader.go`](https://github.com/ssp-data/neomd/blob/main/internal/ui/reader.go) — `renderEmailHeader()`
9494+**Code:** [`internal/imap/client.go`](https://github.com/ssp-data/neomd/blob/main/internal/imap/client.go) — `detectSpyPixels()`, `SpyPixelInfo` · [`internal/ui/inbox.go`](https://github.com/ssp-data/neomd/blob/main/internal/ui/inbox.go) — `⊙` indicator · [`internal/ui/reader.go`](https://github.com/ssp-data/neomd/blob/main/internal/ui/reader.go) — `renderEmailHeader()`
96959796---
9897
+1-1
docs/content/docs/reading.md
···4040- `⊙` indicator in the inbox list (orange, next to the attachment `@` column)
4141- `⊙ N spy pixel(s) blocked (domain.com)` in the reader header with tracker domains
42424343-The browser view (`O`) also blocks remote images via a Content-Security-Policy header, so tracking pixels are blocked even when viewing the full HTML version.
4343+When you press `O` to open in the browser, remote images load normally — you're explicitly choosing to see the full email. Tracking pixel blocking is a TUI-level protection.
44444545### This is how it looks:
4646
-23
internal/render/html.go
···2020<head>
2121<meta charset="UTF-8">
2222<meta name="viewport" content="width=device-width,initial-scale=1.0">
2323-<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src file: data: cid:; font-src 'none';">
2423<style>
2524body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;line-height:1.6;color:#333;margin:0;padding:8px 16px;text-align:left}
2625a{color:#3150AA;text-decoration:underline}
···6059 ),
6160 goldmark.WithRendererOptions(html.WithHardWraps()),
6261)
6363-6464-// cspMeta is the Content-Security-Policy meta tag injected into all browser views.
6565-// Blocks remote images (tracking pixels), scripts, and fonts.
6666-const cspMeta = `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src file: data: cid:; font-src 'none';">`
6767-6868-// InjectCSP inserts a CSP meta tag into an HTML document for safe browser viewing.
6969-// If the document has a <head>, the tag is inserted after it. Otherwise it's prepended.
7070-func InjectCSP(html string) string {
7171- if idx := strings.Index(strings.ToLower(html), "<head>"); idx >= 0 {
7272- insert := idx + len("<head>")
7373- return html[:insert] + "\n" + cspMeta + html[insert:]
7474- }
7575- if idx := strings.Index(strings.ToLower(html), "<html"); idx >= 0 {
7676- // Find the end of the <html...> tag
7777- end := strings.IndexByte(html[idx:], '>')
7878- if end >= 0 {
7979- insert := idx + end + 1
8080- return html[:insert] + "<head>" + cspMeta + "</head>" + html[insert:]
8181- }
8282- }
8383- return cspMeta + "\n" + html
8484-}
85628663// ToHTML converts a Markdown string to a complete HTML email document.
8764func ToHTML(markdown string) (string, error) {