Skip to content

Feature Remote images

SkimMail docs edited this page Sep 18, 2026 · 7 revisions

English · Tiếng Việt · 中文

Remote images in mail

Since 1.11.0 SkimMail can show the images a message links to. It fetches them itself — through the same egress that account uses for mail — so the sender's server is talking to your instance and never to your browser. Nothing is fetched until you ask: every message still arrives with its remote images withheld.

What this solves

A remote image in an email is a receipt. The moment your mail client loads it, the sender learns the message was opened, roughly when, and from roughly where. That is why SkimMail has always stripped them.

What it did not have was a way to change your mind. A shipping confirmation that is nothing but images, a ticket, a scanned invoice — all unreadable, with no button anywhere. 1.11.0 keeps the default (withhold) and adds the way out: per message, per sender, and an instance-wide switch for the operator who wants none of it. In 1.11.0 half of that lived only in the API; since 1.11.1 every control has a screen.

The three layers, and which one wins

Decision Who can set it How far it reaches Beaten by
Remote images off for the instance owner every user, every message nothing
never for a sender operator, owner that user's mail from that sender the instance switch
always for a sender operator, owner that user's mail from that sender the instance switch, never
Ask — the default any signed-in reader one message, until you move away everything above

The order is not arbitrary:

  • The instance switch wins over everything, including a sender you personally marked "always". An administrator who turns remote images off has made a privacy decision for the whole instance, and a preference saved last month must not quietly reopen it.
  • never beats always. Between two standing decisions, the one that fetches nothing is the safe way to be wrong.
  • "Show images" on one message is never written down. Showing an image once is not a standing grant to a sender nobody has vouched for — that is what the second button is for. Open another message and it resets.

Per-sender decisions belong to the user who made them. In AUTH_MODE=users, two people can disagree about the same newsletter.

Showing images in one message

When a message has remote images and no standing decision covers it, the reading pane shows a bar above the body:

🛡 Remote images blocked · Show images · Always from this sender · Never from this sender

Show images reloads that one body with the images routed through SkimMail's proxy. It applies to the message you are looking at, in this browser, until you open another one.

If the instance switch is off there is no button — only a line saying so, because there is nothing you could press that would work. If the sender is on your never list you get a different line, with a way back; see below.

Trusting a sender for good

Always from this sender records a standing decision for that sender address and loads images immediately, on this and every later message from them. Never from this sender, beside it since 1.11.1, records the opposite: nothing from that address is ever fetched again.

A sender you have refused shows this instead of the buttons:

🛡 Images from this sender are never loaded · Change this

Change this moves the sender from never to always — it is an undo for a decision made in one careless click, not a reset. Sending them back to asking each time is the one thing with no button; it is "" over the API, below.

Three things worth knowing:

  • Both buttons need the operator or owner role. On a viewer account the button is drawn but the request is refused, and the one-off reveal stays as it was. See Users and roles.
  • The decision is keyed on the From address, lower-cased. It is not a domain rule and not a pattern: news@example.com and noreply@example.com are two separate decisions.
  • Per-sender decisions belong to the user who made them, so under AUTH_MODE=users your never list is yours alone.

Before 1.17.0, that lookup compared against the wrong string for any sender with a display name — which is nearly all of them. It read the message's whole From field, Name <addr@host>, and checked it against the bare address stored on the sender record, so a standing never (or always) silently did nothing for those messages — they fell back to asking every time, the same as a sender with no decision at all. Fixed in 1.17.0 (SKIMMAIL-189): the lookup now extracts the address first, the same way setting the policy always did.

To go back to asking each time, or to script any of this, use the API. Sign in first and keep the session cookie:

curl -sS -c /tmp/skim.cookies -H 'Content-Type: application/json' \
  -d '{"username":"owner","password":"YOUR-PASSWORD"}' \
  http://localhost:8080/api/auth/login

In passphrase mode the username is ignored; send the passphrase as password. If two-factor authentication is enrolled, this call returns a challenge and the browser is the easier path.

Then set the standing decision for one sender:

curl -sS -b /tmp/skim.cookies -X POST -H 'Content-Type: application/json' \
  -d '{"policy":"never"}' \
  http://localhost:8080/api/senders/newsletter@example.com/images

"policy" takes "always", "never", or "" to go back to asking each time. The empty string is the only one of the three with no button in the interface.

Turning remote images off for the whole instance

The instance switch is on by default — "on" meaning readers are allowed to ask. Turning it off means no image is ever fetched for anybody, whatever any per-sender preference says, and the proxy refuses the request even if somebody calls it directly.

Since 1.11.1 it is a checkbox: Settings ▸ Security ▸ Remote images ▸ Allow remote images. Only an owner can change it; everyone else sees the current state with the control disabled. It is shown even when AUTH_MODE=none, because it is a property of the instance rather than of an identity — an instance nobody signs in to still fetches images.

On 1.11.0 there was no screen at all. The same switch over the API, for a script or for that release:

curl -sS -b /tmp/skim.cookies -X PUT -H 'Content-Type: application/json' \
  -d '{"remote_images":false}' \
  http://localhost:8080/api/settings/reading

Read it back — this one is allowed for any signed-in user:

curl -sS -b /tmp/skim.cookies http://localhost:8080/api/settings/reading

Readers then see "Remote images are turned off for this instance" where the buttons used to be. Set true to allow asking again; no per-sender decision is lost while the switch is off.

What leaves your server when you press Show images

  • SkimMail fetches, your browser does not. The body is served as its own document with a policy that permits images from SkimMail's own proxy and nothing else at all — no scripts, no fonts, no frames, no connections.
  • The fetch uses that account's egress. If you routed the account through a SOCKS5/HTTP proxy, a WireGuard tunnel or a relay, the image leaves the same way its mail does. An account you carefully put behind a proxy is not exposed by a tracking pixel.
  • Almost nothing goes with the request. Accept: image/* and User-Agent: SkimMail. No cookies, no Referer, no Accept-Language — a fingerprint, and often a country.
  • The address is resolved by SkimMail, not by your proxy, and the connection is made to the address that was checked. Loopback, private, link-local (including the cloud metadata address), unspecified and multicast addresses are refused — so <img src="http://192.168.1.1/"> in a stranger's mail cannot make your own proxy reach into your own network.
  • A shown image stays in your browser's cache for an hour. That is a privacy setting, not a performance one: every miss is another touch on the sender's server.

What the sender still learns, and this page will not pretend otherwise: that the message was opened, when, and the public IP that account's egress leaves from. Remote images remain a tracking mechanism. SkimMail changes who is visible, not whether something is.

What changed in 1.18.0

Before 1.18.0, pressing Show images did nothing, for any message, ever. The reading pane renders each body inside a locked-down frame, and a side effect of that lock-down was that the browser stopped attaching your session to anything the frame asked for — so every image request came back "not signed in" before SkimMail's server even tried to fetch the picture, and the image stayed broken however many times you pressed the button or how healthy the sender's server was. Fixed in 1.18.0 (SKIMMAIL-209): the frame is now same-origin for exactly the requests that need your session, and still locked down in every other way that matters — nothing in a message can run code, submit a form, open a window, or navigate anywhere. The one new thing that same-origin change opened, and how it was closed, is on Security.

A fetch that fails now says why, in the server log. Until this release nothing recorded the reason a remote image did not load — eight different causes, from a blocked address to a dead server to a file that was not really an image, all produced the same empty response and not one log line. Each now names itself. Only the image's host is recorded, never its full address — a link in mail is usually unique to the recipient, and the log can be downloaded.

Limits

Formats served PNG, JPEG, GIF, WebP, BMP, ICO
Always refused SVG, AVIF, anything else
Size per image 5 MiB
Time per image 10 seconds
Redirects at most four, each one re-checked
Images per message the first 200
Browser cache 1 hour
  • The type is decided by looking at the bytes, not by believing the sender's Content-Type. An SVG served as image/png is still refused.
  • SVG is refused on purpose. It is a document, not a picture: an SVG carrying a script, served from SkimMail's own origin, would be stored XSS in your session.
  • AVIF is refused for a duller reason. Go's content sniffer cannot recognise the format, so allowing it would mean trusting the sender's label — the one thing this design does not do. An AVIF image shows as broken and there is no setting that changes it in 1.11.0.
  • Images embedded in the message itself are not shown — only http:// and https:// sources go through the proxy. cid: and data: sources are removed by the sanitiser, and relative addresses (/logo.png) are withheld silently, because under the body document they would become requests to SkimMail's own API carrying your session.
  • If the account's egress is an HTTP proxy that refuses CONNECT on port 80, plain-http:// images cannot be fetched through it. HTTPS is unaffected.
  • A refused image draws the browser's broken-image mark, never a blank pixel. That is deliberate: a blank pixel is indistinguishable from a tracking pixel that loaded successfully, and you would have no way to tell "refused" from "the sender got their receipt".

What it does not do

  • It is not a general-purpose proxy. The endpoint takes an index into the images of a message you are already allowed to read — never a URL. That is what stops anyone who can send you mail from turning your instance into an open relay, and it is why there is no "fetch this address" affordance anywhere.
  • It does not prefetch images. Nothing is requested until a human presses the button, or the sender already carries an always.
  • It has no per-account switch. The instance switch is instance-wide; sender decisions follow the user, not the mailbox.
  • There is no list of the senders you have decided about. The interface shows the decision on a message from that sender and nowhere else; auditing or clearing them in bulk is not something 1.15.0 can do. The senders panel added to Settings ▸ Security in 1.13.0 is a different list — it holds muted and VIP senders only, not image decisions, even though both are stored against the same correspondent. See Muted and VIP senders.
  • It does not remember "show once" — by design, not by omission.
  • It does not make opening mail private. See the paragraph above.

See also

  • Security — the threat model this sits inside, and encryption at rest
  • Users and roles — why the sender buttons need operator
  • Troubleshooting — images that stay broken after you press Show images
  • Configuration — per-account egress and the environment reference

SkimMail · skimmail@base101.app · 2026-09-18 · commit f525934

Clone this wiki locally