Skip to content

Conversation

mariuso
Copy link

@mariuso mariuso commented Jul 6, 2025

This PR adds connection lifecycle hooks to enable monitoring, metrics, and debugging of SMTP connections.

What's new:

  • ConnState callback field on Server that gets called when connections change state
  • Connection states: New, Active, Auth, Data, StartTLS, Reset, Idle, Error, Closed
  • Each state transition triggers the callback with the connection and new state

Use cases:

  • Track active connections count
  • Monitor connection duration and state transitions
  • Debug authentication issues and connection problems
  • Build metrics dashboards for SMTP server health

Example:

server.ConnState = func(conn net.Conn, state smtp.ConnState) {
    switch state {
    case smtp.StateNew:
        activeConnections.Inc()
    case smtp.StateClosed:
        activeConnections.Dec()
    }
}

No breaking changes - the callback is optional and everything works exactly the same if you don't set it.

Includes comprehensive tests showing all state transitions.

Addresses issue emersion#273 and emersion#282 where there was no way to observe
connection lifecycle events for monitoring, metrics, and operational
visibility.

Changes:
- Add ConnState type with 9 states (New, Active, Auth, Data, etc.)
- Add ConnState callback field to Server struct
- Implement state transitions at key connection points
- Add comprehensive test suite for all state transitions
- Add documentation and example usage

This enables connection tracking, metrics integration, and graceful
shutdown coordination similar to net/http.Server.ConnState.

Related: emersion#273
Related: emersion#282
@arp242
Copy link
Contributor

arp242 commented Jul 9, 2025

I think c.setState(StateError) can be in the Conn.WriteResponse()? That is:

func (c *Conn) writeResponse(code int, enhCode EnhancedCode, text ...string) {
	if code >= 400 {
		c.setState(StateError)
	}

	[..]
}

There's a bunch of error replies where you're not calling setState(stateError) though, I'm not sure if that's intentional?

}

err := s.handleConn(conn)
if err != nil && conn.hasReceivedData {
Copy link
Contributor

@arp242 arp242 Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't compile because conn.hasReceivedData doesn't exist; seems to be a left-over of your other PR (#285)?

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.

2 participants