Skip to main content
Inbox Zero can proxy remote email images and CSS-loaded assets so senders do not see the end user’s IP address when an email is rendered. This is optional, but recommended if you host Inbox Zero for other people.

What It Does

When enabled:
  • Remote img, srcset, background, poster, inline style, and <style> asset URLs are rewritten at render time.
  • The browser loads those assets from your proxy instead of directly from the sender-controlled host.
  • The iframe renderer also applies a restrictive CSP so direct remote fetches are blocked even if the rewriter misses a case.
The proxy is a separate service from the main app. The app signs proxy URLs, and the proxy fetches the upstream image.

Deploy On Cloudflare Workers

The repo includes a ready-to-deploy Worker in apps/image-proxy.

1. Log In To Cloudflare

cd apps/image-proxy
pnpm exec wrangler login
pnpm exec wrangler whoami

2. Create a Signing Secret

Use the same secret in both the Worker and the web app:
openssl rand -hex 32

3. Set the Worker Secret

cd apps/image-proxy
pnpm exec wrangler secret put IMAGE_PROXY_SIGNING_SECRET

4. Deploy Without Editing Any Repo Files

Replace img.example.com with your own hostname:
cd apps/image-proxy
pnpm exec wrangler deploy --domains img.example.com
This is the recommended self-hosting path because it avoids editing wrangler.jsonc just to choose a domain.

5. Configure the Inbox Zero App

In your app environment:
NEXT_PUBLIC_IMAGE_PROXY_BASE_URL=https://img.example.com/proxy
IMAGE_PROXY_SIGNING_SECRET=your-generated-secret
The values must match:
  • NEXT_PUBLIC_IMAGE_PROXY_BASE_URL tells the app where to send proxied asset requests.
  • IMAGE_PROXY_SIGNING_SECRET must match the Worker secret so the proxy can validate signed URLs.
If you need separate secrets for different clients, configure each signer with its own single IMAGE_PROXY_SIGNING_SECRET, and configure the proxy validator with a comma-separated list. Example:
# web app signer
IMAGE_PROXY_SIGNING_SECRET=server-secret

# desktop app signer
IMAGE_PROXY_SIGNING_SECRET=desktop-secret

# proxy validator
IMAGE_PROXY_SIGNING_SECRET=server-secret,desktop-secret

Unsigned Custom Proxy Mode

If you set NEXT_PUBLIC_IMAGE_PROXY_BASE_URL without IMAGE_PROXY_SIGNING_SECRET, Inbox Zero emits unsigned ?u= proxy URLs. That mode is only for a custom proxy that intentionally accepts unsigned requests. The bundled Cloudflare Worker expects signed URLs when IMAGE_PROXY_SIGNING_SECRET is set. For production use, signed mode is recommended.

AWS Option

If you do not want Cloudflare, the repo also includes an AWS Lambda adapter in apps/image-proxy-aws. The deployment model is:
  • deploy the Lambda
  • put CloudFront in front of it
  • set NEXT_PUBLIC_IMAGE_PROXY_BASE_URL to your CloudFront URL
  • use the same IMAGE_PROXY_SIGNING_SECRET

Self-Hosted Next.js Option

If you are self-hosting and want the simplest setup, Inbox Zero can proxy remote images through its own Next.js app route instead of a separate service. Set these app environment variables:
NEXT_PUBLIC_IMAGE_PROXY_USE_APP_ROUTE=true
IMAGE_PROXY_SIGNING_SECRET=your-generated-secret
In this mode, Inbox Zero signs image proxy URLs and routes them to ${NEXT_PUBLIC_BASE_URL}/api/image-proxy. Notes:
  • this runs in the main app process, so it is simpler but less isolated than a separate proxy service
  • the Next.js route uses the same SSRF protections as the AWS adapter, including DNS resolution and pinned public-address lookups
  • keep IMAGE_PROXY_SIGNING_SECRET set; production rewriting stays disabled without it

Troubleshooting

Health Check

Once deployed, this should return 200:
curl -I https://img.example.com/health

A Specific Image Fails

If an upstream image has redirect issues or unsupported content, the image will fail closed instead of leaking the user’s IP. The Worker logs redirect-chain failures with sanitized URLs that omit query strings, which helps debug upstream redirect loops without logging tracking parameters.

The App Still Loads Images Directly

Double-check:
  • NEXT_PUBLIC_IMAGE_PROXY_BASE_URL is set
  • the browser is rendering email through the normal HTML email viewer
  • your proxy hostname is reachable from the browser
For most self-hosters:
  1. Deploy the main app normally.
  2. Choose one proxy mode:
  3. Simplest: set NEXT_PUBLIC_IMAGE_PROXY_USE_APP_ROUTE=true and IMAGE_PROXY_SIGNING_SECRET.
  4. More isolated: deploy the image proxy separately on Cloudflare Workers and set NEXT_PUBLIC_IMAGE_PROXY_BASE_URL plus IMAGE_PROXY_SIGNING_SECRET.
The in-app route is the easiest self-hosted setup. The separate proxy service is still the better fit when you want stronger isolation or independent scaling.