Reading a message routes its remote images through this server, so the sender learns nothing about the reader. Quoting the same message into a reply did not: reply() passed proxyRemote: false, so once images were allowed the composer fetched them straight from wherever the mail pointed — same pixel, same reader, but now carrying their IP and user agent. openDraftEmail and composeAsNew did the same.
A quote is proxied the way the message view is, following the deployment's own imageProxy setting.
The sent copy is not. A proxied URL belongs to this deployment; left in, the recipient would get images only this server can serve — broken for them, a beacon back here for anyone who could load them. buildEmailObject turns them back into the addresses they came from, beside the pass that restores images blocked under #411 and the one that turns editor blob URLs into cid: references.
Proxy off, nothing changes: the quote fetches directly, as reading does there.
Related issues
Fixes#412. Follows #411, where this was noted and deliberately left.
Translations
Adds none.
Testing
quote-image-proxy.test.ts: the quote is fetched through the server with the proxy on, directly with it off, and on a forward too; the sent copy carries the image's own address and no /api/image URL; a signature or template image that used the proxy is restored the same way; cid:, blob: and ordinary links are left alone. Two of these fail on the previous code.
Three tests from #411 asserted the address sat in src when images were allowed — the old behaviour. They now ask whether the draft fetches the image at all, proxied or not, which is what they were really about.
npm test (1430 web, 251 server) and npm run typecheck pass.
Driven in Chrome against npm run dev:mock, on a seeded message carrying https://example.com/tracker.gif, images allowed. The quote's src was /api/image?url=… and the network log shows both requests going to localhost, none to example.com. Sending the reply and reading it back out of the mock's Sent folder: the stored copy carries https://example.com/tracker.gif, with no /api/image URL and no blocked markers left behind.
## Summary
Reading a message routes its remote images through this server, so the sender learns nothing about the reader. Quoting the same message into a reply did not: `reply()` passed `proxyRemote: false`, so once images were allowed the composer fetched them straight from wherever the mail pointed — same pixel, same reader, but now carrying their IP and user agent. `openDraftEmail` and `composeAsNew` did the same.
- **A quote is proxied the way the message view is**, following the deployment's own `imageProxy` setting.
- **The sent copy is not.** A proxied URL belongs to this deployment; left in, the recipient would get images only this server can serve — broken for them, a beacon back here for anyone who could load them. `buildEmailObject` turns them back into the addresses they came from, beside the pass that restores images blocked under #411 and the one that turns editor blob URLs into `cid:` references.
- **Proxy off, nothing changes:** the quote fetches directly, as reading does there.
## Related issues
Fixes #412. Follows #411, where this was noted and deliberately left.
## Translations
Adds none.
## Testing
- `quote-image-proxy.test.ts`: the quote is fetched through the server with the proxy on, directly with it off, and on a forward too; the sent copy carries the image's own address and no `/api/image` URL; a signature or template image that used the proxy is restored the same way; `cid:`, `blob:` and ordinary links are left alone. **Two of these fail on the previous code.**
- Three tests from #411 asserted the address sat in `src` when images were allowed — the old behaviour. They now ask whether the draft fetches the image at all, proxied or not, which is what they were really about.
- `npm test` (1430 web, 251 server) and `npm run typecheck` pass.
- Driven in Chrome against `npm run dev:mock`, on a seeded message carrying `https://example.com/tracker.gif`, images allowed. The quote's `src` was `/api/image?url=…` and **the network log shows both requests going to localhost, none to example.com**. Sending the reply and reading it back out of the mock's Sent folder: the stored copy carries `https://example.com/tracker.gif`, with no `/api/image` URL and no blocked markers left behind.
**Merged** 2026-09-19 as coffey-labs/ihasmail@23557a72a2a7
<sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Reading a message routes its remote images through this server, so the sender learns nothing about the reader. Quoting the same message into a reply did not:
reply()passedproxyRemote: false, so once images were allowed the composer fetched them straight from wherever the mail pointed — same pixel, same reader, but now carrying their IP and user agent.openDraftEmailandcomposeAsNewdid the same.imageProxysetting.buildEmailObjectturns them back into the addresses they came from, beside the pass that restores images blocked under #411 and the one that turns editor blob URLs intocid:references.Related issues
Fixes #412. Follows #411, where this was noted and deliberately left.
Translations
Adds none.
Testing
quote-image-proxy.test.ts: the quote is fetched through the server with the proxy on, directly with it off, and on a forward too; the sent copy carries the image's own address and no/api/imageURL; a signature or template image that used the proxy is restored the same way;cid:,blob:and ordinary links are left alone. Two of these fail on the previous code.srcwhen images were allowed — the old behaviour. They now ask whether the draft fetches the image at all, proxied or not, which is what they were really about.npm test(1430 web, 251 server) andnpm run typecheckpass.npm run dev:mock, on a seeded message carryinghttps://example.com/tracker.gif, images allowed. The quote'ssrcwas/api/image?url=…and the network log shows both requests going to localhost, none to example.com. Sending the reply and reading it back out of the mock's Sent folder: the stored copy carrieshttps://example.com/tracker.gif, with no/api/imageURL and no blocked markers left behind.Merged 2026-09-19 as coffey-labs/ihasmail@23557a72a2
Rebuilt from: git history, session transcript.