Skip to content

fix: listen to mail.tm's push stream instead of polling into its rate limit - #5

Merged
brentc22 merged 3 commits into
mainfrom
fix/mailtm-rate-limit
Oct 12, 2026
Merged

brentc22 merged 3 commits into
mainfrom
fix/mailtm-rate-limit

Conversation

@brentc22

Copy link
Copy Markdown
Owner

Why

mail.tm now allows 30 requests a minute per IP (ratelimit-policy: 30; w=60). Every catchbox server polled every mailbox every 4 s — 150/min per server, and the Mac app and the catchbox ui LaunchAgent each run one. Opening a message got a 429, so the reading pane stayed on the previous mail; "All mailboxes" came back silently empty.

What

  • One Mercure (server-sent events) stream per mailbox; an event refreshes only that mailbox, debounced, and skipped when a check already started after it.
  • The inbox is served from the cached list while the stream is up, or when it was fetched in the last 20 s.
  • Safety net: a mailbox with a live stream is checked every 5 min, one without every 20 s, at most 3 per tick.
  • Reconnects catch up on what they missed; an idle watchdog (75 s, Mercure sends a keep-alive every 30 s) drops dead sockets; outages are logged once.
  • Mutations refresh only the mailboxes they touched; deleted/replaced mailboxes leave no state behind.
  • Version 1.4.1.

Measured locally: after the start-up check, 0 requests a minute to mail.tm with 10 mailboxes (was ~150).

Test plan

  • node --test — 53/53, five new tests (cache while live, debounced burst, split keep-alive, reconnect catch-up, no double refresh on the server's own change)
  • Live server on :7338 — all 10 streams up, steady state 0 requests/min, clicking the peppol mail opens it
  • After release: app self-update to 1.4.1, brew upgrade catchbox + restart the LaunchAgent

🤖 Generated with Claude Code

brentc22 and others added 3 commits October 12, 2026 15:54
… limit

mail.tm now allows 30 requests a minute per IP. Polling every mailbox every
4 seconds sent 150 a minute per server (and the app and the LaunchAgent each
run one), so opening a message got a 429 and the reading pane stayed on the
previous mail; the All mailboxes view silently came back empty.

Each mailbox now has a Mercure stream; an event refreshes only that mailbox
(debounced). The inbox is served from that cache while the stream is up, a
safety-net check runs every 5 minutes per live mailbox and never more than
3 per 20 s, and mutations refresh only the mailboxes they touched.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- a reconnect refreshes unless a check started after the stream went down
- an idle watchdog (75 s, Mercure keeps alive every 30 s) drops dead sockets
- the inbox is also served from a list fetched in the last 20 s, so a Mercure
  outage does not bring back a request per mailbox per page load
- a 401 from Mercure renews the token at most every 5 minutes; failed
  responses release their body
- events are parsed per line and only data: lines count
- a push event is skipped when a check already started after it, so the
  server's own changes no longer cost a second refresh
- an expired mailbox replaced mid-refresh gets its stream, and deleted
  mailboxes leave no state behind
- stream outages are logged once, not swallowed
- version 1.4.1

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@brentc22
brentc22 merged commit a2c5bc3 into main Oct 12, 2026
5 checks passed
@brentc22
brentc22 deleted the fix/mailtm-rate-limit branch October 12, 2026 14:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant