Skip to content

mail: cap an outgoing message at the size the doors already accept - #1467

Open
Dev9269 wants to merge 1 commit into
micro:mainfrom
Dev9269:fix/outgoing-size-cap
Open

mail: cap an outgoing message at the size the doors already accept#1467
Dev9269 wants to merge 1 commit into
micro:mainfrom
Dev9269:fix/outgoing-size-cap

Conversation

@Dev9269

@Dev9269 Dev9269 commented Aug 24, 2026

Copy link
Copy Markdown

One limit on mail.Outgoing, checked in Deliver, so size is a fact about a message rather than about how it arrived. 10MB, matching what the SMTP paths already accept (smtp.go, submission.go) and the 1MB the JSON API caps at today.

Refused with the size and the limit, per the issue. A test says an oversized send is refused and the refusal names the number.

Fixes #1465

One limit on mail.Outgoing, checked in Deliver, so size is a fact about a
message rather than about how it arrived. 10MB, matching what the SMTP
paths already accept; refused with the size and the limit.

Fixes micro#1465

asim commented Aug 25, 2026

Copy link
Copy Markdown
Member

The cap is in the right place, but it never fires — the test is one byte short of testing anything.

big := strings.Repeat("x", maxOutgoingBytes)   // exactly 10485760
...
if n := len(m.Body) + len(m.HTML); n > maxOutgoingBytes {   // 10485760 > 10485760 == false

Run on this branch:

--- FAIL: TestDeliverRefusesAnOversizedMessage
    deliver_test.go:282: error "no account here called \"someone\"" does not say the size or the limit

That error is the whole diagnosis. The first assertion (err == nil) passed on a completely unrelated failure: someone@example.test is not an account here, so deliverHere refused it long before size mattered. Only the second assertion — that the message names the limit — caught that the cap had done nothing.

Worth noting because it nearly went the other way: had the test asserted only err != nil, which is the usual shape, it would be green and the cap would be dead code. The size refusal and the missing-recipient refusal come out of the same call, so nothing distinguishes them except the text. Checking that an error says the right thing earns its keep here.

Two changes and this is good:

  1. strings.Repeat("x", maxOutgoingBytes+1). Keep > rather than moving to >= — a message exactly at the limit should be accepted, which is what a limit means.
  2. Create the recipient account in the test, so a future regression can't be masked by the same unrelated error again.

What checks out, for the record: fmt is already imported in deliver.go; Outgoing carries no attachment, so len(Body)+len(HTML) really is the whole message; and the check sits before the external/local branch, so both routes are covered — which is the right argument for putting it in Deliver rather than at each of the five doors.

Two small things about the description rather than the code:

  • The 10MB figure is correct (smtp.go:1055 and submission.go:74 both set MaxMessageBytes = 1024 * 1024 * 10). The 1MB is internal/api/rest.go:172, which caps a JSON request body — a different limit on a different door. Citing both together reads as though they disagree.
  • 10MB of body is not 10MB on the wire: headers, MIME framing and quoted-printable expansion push it over, so a message that passes this cap can still be rejected by the SMTP server sharing the number. Not a problem — accepting slightly-too-large and failing later beats truncating — but the constant's comment claiming it "matches" what SMTP accepts is a little optimistic, and a word about the direction of the mismatch would age better.

Generated by Claude Code

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.

Nothing caps how big a stored message may be, so one caller can permanently enlarge the store

2 participants