Repository navigation
Check and record quotas for sends and local delivery - #50
Merged
Merged
Conversation
Mail between local addresses never passes through fmsgd, so it was neither checked against the recipients' receive limits nor recorded, and nothing checked or recorded send limits. Local delivery now makes fmsgd's checks for each local recipient: one more message of the message's stored size (body plus attachments) must fit its messages per day, bytes per day and total bytes received. A recipient over a limit isn't delivered and gets response code 101 (user full), as fmsgd records it, alongside 100 and 102 as before. Each delivered recipient is recorded with POST /fmsgid/recv. Send and react check the sender's per-message size, messages per day, bytes per day and total bytes sent before sending, refusing with 413, 429 or 422 and a code naming the limit, and record the message once with POST /fmsgid/send. A reaction is a message like any other. Add-to doesn't send a new message, so it isn't counted as a send, but its new local recipients are checked and counted. The checks fetch fmsgid's detail fresh each time instead of using the 30s lookup cache, since usage changes with every message. Failing to record usage is logged and doesn't fail the request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Mail between local addresses never passes through fmsgd: this service delivers it itself (
resolveLocalDelivery). It checked only whether each recipient exists and accepts new messages, and recorded no usage. Nothing checked or recorded send limits either. This PR makes this service count everything fmsgd doesn't. The companion fmsgd PR (markmnl/fmsgd#50) makes fmsgd count attachments and use the accept time.What
Local delivery (send, react, add-to on a sent message): each local recipient gets fmsgd's checks.
100and not accepting is102, as before.101(user full).time_deliveredstays null), and its code is recorded inresponse_code, soto_delivery/add_toshow101the way fmsgd records rejected recipients.POST /fmsgid/recv, at the delivery time. Usage is recorded only when this request's update resolved the row, so a row that was already resolved is never counted twice.Sending (
POST /fmsg/:id/send,POST /fmsg/:id/react): before sending, the sender's current limits and usage are fetched from fmsgid. The message is refused, and left a draft, if it is:codeerror413send_size_per_msg_limit429send_count_per_1d_limit429send_size_per_1d_limit422send_size_total_limitPOST /fmsgid/send, however many recipients it has.503instead of going out unchecked.403, the same answer authentication gives.Rules chosen:
Cache. The existing 30s lookup cache (
CheckFmsgID) holds only whether an address exists and accepts new messages, for authentication. Quota checks use the newFetchFmsgIDDetail, which bypasses the cache every time, because usage changes with every message. As a side effect, a fresh detail also refreshes the cached lookup. The cost is one extra fmsgid GET per send and per local recipient, the same as fmsgd's per-recipient lookup.Docs
The README has a new Quotas section, and the send, add-to and react sections list the new behaviour and errors.
Tests
quota_test.gocovers recipient codes and every receive and send limit's boundaries and ordering.fmsgid_usage_test.gocovers that the detail is never served from the cache, that missing limits default to unlimited, invalid responses, the usage payloads and a failed record.quota_integration_test.go, run against a throwaway Postgres 17 withFMSG_TEST_DATABASE_URL):101and aren't delivered, an unknown one gets100, and a remote one is left to fmsgd;to_deliveryshows101;503.go vet ./...,gofmtclean, andgo test ./...pass, with and without the database.Deploy
No schema change. It needs an fmsgid whose
GET /fmsgid/:addressreturns limits and usage, which every fmsgid does; the counter fixes in markmnl/fmsgid#6 make those numbers right. Deploy after fmsgid and fmsgd.Known gap (unchanged, follow-up)
The inbox (
GET /fmsgand friends) lists every message an address is a recipient of, delivered or not. So a recipient refused with101(here, or by fmsgd for mail with another local recipient) still sees the message, though it isn't delivered, pushed or counted.🤖 Generated with Claude Code