https://github.com/thunderbird/avatars
Sender-domain avatar proxy for Thunderbird webmail
https://github.com/thunderbird/avatars
Last synced: 24 days ago
JSON representation
Sender-domain avatar proxy for Thunderbird webmail
- Host: GitHub
- URL: https://github.com/thunderbird/avatars
- Owner: thunderbird
- License: mpl-2.0
- Created: 2026-05-24T00:18:21.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-05-24T02:59:59.000Z (3 months ago)
- Last Synced: 2026-07-09T13:28:33.414Z (30 days ago)
- Language: TypeScript
- Size: 45.9 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# avatars
Cloudflare Worker that serves sender-domain avatar images for Thunderbird
webmail.
The service accepts a normalized domain on `avatars.thunderbird.net` and
always returns a PNG, falling back to a 1x1 transparent pixel on errors:
```text
https://avatars.thunderbird.net/example.com?v=3
```
The optional `v` query parameter is a cache version chosen by the caller.
Changing it forces a fresh edge/browser cache entry when avatar lookup
behavior changes. It is not sent to upstream providers.
## Behavior
- Accepts only `GET`, `HEAD`, and `OPTIONS`. Other methods get a 405 fallback.
- Normalizes and validates the domain before making any upstream request.
- Fetches favicons only from `https://www.google.com/s2/favicons`. Because
Google reliably redirects this endpoint to its CDN, redirects are followed
and the final response is required to come from `www.google.com` or a
`*.gstatic.com` subdomain over HTTPS. Bare `gstatic.com` and any `http:`
downgrade are rejected.
- Returns only PNG bodies up to 256 KiB, validating the PNG signature, chunk
structure, IHDR/PLTE/IDAT/IEND ordering, color-type/bit-depth combinations,
and CRC-32 of every chunk before responding or caching. Provider response
headers are not trusted.
- Successful responses, validation failures, and Google 4xx responses are all
cached in Cloudflare's default Cache API under a versioned key
(`/__cache//`). Validation failures are negative-
cached for one hour; transient network failures (502) are not cached.
- `HEAD` requests share the `GET` cache and populate it on miss, so a `HEAD`
costs no more than a `GET` and never bypasses the edge.
- Sends wildcard CORS headers, `X-Content-Type-Options: nosniff`, and
`Cross-Origin-Resource-Policy: cross-origin` on every PNG response.
- Keeps Worker observability logging disabled.
### Known limitations
- Google's favicon service responds with a generic "globe" placeholder for
domains it does not know about. The worker has no signal to distinguish
this from a real favicon and will cache the placeholder under the requested
domain for the configured TTL. Operators that want to fail-closed for
unknown domains should change the `v` query parameter or bump
`AVATAR_CACHE_PROVIDER_VERSION` after fixing the heuristic.
## Development
Use Node.js 24 or newer.
```bash
npm ci
npm run typecheck
npm test
```
Run locally with Wrangler:
```bash
npx wrangler dev
```
## Deployment
The production custom domain `avatars.thunderbird.net` is attached to the
Worker once in Cloudflare using human credentials. The steady-state GitHub
Actions deploy updates only the Worker script, so the CI token does not
need DNS or Worker route permissions.
Required repository secrets:
- `CLOUDFLARE_ACCOUNT_ID`
- `CLOUDFLARE_API_TOKEN`
The deploy token is scoped with only Workers script write/edit permissions.