-
Notifications
You must be signed in to change notification settings - Fork 37
Description
I'm attempting to solve the 421 Connection closed by server. Maximum message count per session reached. errors. Discussed in this thread: knadh/listmonk#1717
I submitted a PR for a retry delay: knadh/listmonk#2309
Chat GPT says it won't fix the issue, and it may be right.
I suggested adding "Max messages per SMTP connection": knadh/listmonk#2683
GPT says the correct fix is not to knadh/listmonk but rather to knadh/smtppool.
What would actually fix SES 421 cleanly
One of these must happen:
✅ Option A (best, architectural)
Add per-connection message limits to smtppool
Close and replace the connection before SES does
This is what we discussed earlier (MaxMessagesPerConn)
This belongs in smtppool, not listmonk
Next step (when you’re ready)
When you follow up about smtppool, I can:
Draft the exact PR for knadh/smtppool
Include SES-specific 421 handling
Add MaxMessagesPerConn cleanly
Make it upstream-acceptable
Should I continue with that? I'm unsure if SES-specific 421 handling is desirable.
EDIT: I asked GPT about the SES-specific part and it said:
That’s a very good instinct, and you’re right to be cautious. Let me clarify what “SES-specific” means here — and how to do this without hard-coding SES behavior or breaking generic SMTP correctness.
You should not add “if SES then …” logic anywhere.
The correct fix is SMTP-generic:
If the server closes the connection or returns a 421 during DATA / send, the client must discard that connection and retry on a fresh one.
That behavior happens to fix SES — but it is valid SMTP behavior for any server.
The full GPT response
That’s a very good instinct, and you’re right to be cautious. Let me clarify what “SES-specific” means here — and how to do this without hard-coding SES behavior or breaking generic SMTP correctness.
Short answer (TL;DR)
You should not add “if SES then …” logic anywhere.
The correct fix is SMTP-generic:
If the server closes the connection or returns a 421 during DATA / send, the client must discard that connection and retry on a fresh one.
That behavior happens to fix SES — but it is valid SMTP behavior for any server.
Why SES triggers this more often
Amazon SES enforces:
-
a maximum number of messages per SMTP session
-
once exceeded, it responds with:
421 Connection closed by server. Maximum message count per session reached. -
and closes the TCP connection
SES is just being explicit about it.
Other SMTP servers may:
- silently drop the connection
- respond with
421,451, or just EOF - enforce limits based on time, message count, or size
SES is not unique — it’s just well-documented and predictable.
What SMTP RFCs say (why this is generic)
From RFC 5321 (SMTP):
-
421 = Service not available, closing transmission channel
-
Client behavior:
- The connection is no longer usable
- The client must reconnect to continue sending mail
- Retrying on the same connection is invalid
So a correct SMTP client must:
- Treat
421as a connection-terminal error - Close and discard that connection
- Retry the message on a new connection (subject to retry policy)
That is not SES-specific — that is SMTP-correct.
What not to do (this would be SES-specific and wrong)
❌ Matching the literal error string:
if strings.Contains(err.Error(), "Maximum message count per session") {
// SES hack
}❌ Hard-coding message limits like “1000 per connection” in listmonk
❌ Detecting SES by hostname and branching behavior
Those would indeed be inappropriate.
What is appropriate (generic, correct, upstreamable)
1. Treat 421 as a dead connection
In smtppool, when a send returns an error that is:
- SMTP code 421
- or EOF / broken pipe during DATA
- or “connection closed” mid-send
then the pool must:
- close and discard that connection
- not return it to the pool
- optionally retry the message once on a new connection
This logic applies to all SMTP servers.
2. Optional (but ideal): connection lifetime limits
Separately from error handling, adding:
MaxMessagesPerConnis also SMTP-agnostic:
-
Many MTAs enforce:
- max messages per session
- max time per session
-
Limiting connection lifetime proactively is standard practice
-
Postfix, Exim, and Sendmail all do this
This is preventive, not SES-specific.
Why listmonk alone can’t fix this correctly
listmonk:
-
Calls
pool.Send(em) -
Does not control:
- which connection is used
- whether it’s dead or alive
- when it’s reused
So:
- retry logic in listmonk cannot guarantee a new connection
- only the pool can do that correctly
That’s why the fix belongs in smtppool.
How to describe this in a PR (important wording)
If/when you open a PR to knadh/smtppool, I strongly recommend language like:
This change improves SMTP correctness by discarding and recreating pooled connections when the server closes the session (e.g. on SMTP 421 responses or unexpected EOF). This behavior is SMTP-generic and required by RFC 5321. It also prevents issues with MTAs that enforce per-session message limits.
Notice:
- No mention of SES
- No provider-specific wording
- Pure protocol correctness
Mental model (why this is the right abstraction)
Think of it like HTTP keep-alive:
- If the server closes the socket
- the client must not reuse it
- and must retry the request on a new connection
SMTP pooling is the same.
SES just happens to be a very visible case where incorrect reuse causes failures.
Bottom line
You’re absolutely right:
- listmonk should remain SMTP-agnostic
- SES should not be special-cased
The correct fix:
- Is protocol-correct
- Belongs in
smtppool - Benefits all SMTP providers
- Happens to fix SES 421 as a consequence
When you’re ready, I can help you:
- phrase the smtppool PR correctly
- implement the logic in a generic way
- anticipate maintainer objections and address them cleanly
You’re thinking about this exactly the right way.
So I'll go ahead with the PR.