Parent
#1
What to build
Control over when mail leaves, and how fast. Two related things: a send window (working days, times of day, quiet periods during which nothing goes out) and a client-side rate limit per mailer.
Both are jobs, not event handlers. Mail that arrives outside its window is recorded as suppressed rather than sent or dropped, and a scheduled release job picks suppressed mail up once the window opens. The rate limit paces a batch by spacing dispatches, so a queue of several hundred messages drains over minutes instead of hitting a provider limit in one burst.
Client-side pacing is not belt-and-braces here: at least one major provider's documentation is internally inconsistent about what happens when its per-minute message rate is exceeded, so the server's behaviour on breach cannot be relied on.
Configuration is the right home for this ticket, and an operator-facing surface comes separately in #11 — database-backed windows and quiet periods with a Filament resource, layered on what is built here exactly as configuration defaults are overridden by database rows in other Moox packages. Two settings stay in configuration permanently even then: the rate limit, because it describes what the provider tolerates rather than a business decision, and the global off switch, because one mistaken edit in a table would release every held message to real recipients. Note also that a database row is not environment-aware — a production dump restored into staging carries its windows along — which is the underlying reason those two are excluded rather than merely inconvenient.
Acceptance criteria
Blocked by
Parent
#1
What to build
Control over when mail leaves, and how fast. Two related things: a send window (working days, times of day, quiet periods during which nothing goes out) and a client-side rate limit per mailer.
Both are jobs, not event handlers. Mail that arrives outside its window is recorded as suppressed rather than sent or dropped, and a scheduled release job picks suppressed mail up once the window opens. The rate limit paces a batch by spacing dispatches, so a queue of several hundred messages drains over minutes instead of hitting a provider limit in one burst.
Client-side pacing is not belt-and-braces here: at least one major provider's documentation is internally inconsistent about what happens when its per-minute message rate is exceeded, so the server's behaviour on breach cannot be relied on.
Configuration is the right home for this ticket, and an operator-facing surface comes separately in #11 — database-backed windows and quiet periods with a Filament resource, layered on what is built here exactly as configuration defaults are overridden by database rows in other Moox packages. Two settings stay in configuration permanently even then: the rate limit, because it describes what the provider tolerates rather than a business decision, and the global off switch, because one mistaken edit in a table would release every held message to real recipients. Note also that a database row is not environment-aware — a production dump restored into staging carries its windows along — which is the underlying reason those two are excluded rather than merely inconvenient.
Acceptance criteria
Blocked by