func (s *Session) loadMailbox() {
	s.logger = s.logger.With().Str("mailbox", s.user).Logger()
	m, err := s.store.GetMessages(s.user)
	if err != nil {
		s.logger.Error().Msgf("Failed to load messages for %v: %v", s.user, err)
	}
	s.messages = m
	s.retainAll()
}
Load the users mailbox
package pop3
import (
	"bufio"
	"fmt"
	"io"
	"net"
	"os"
	"strconv"
	"strings"
	"time"
	"github.com/inbucket/inbucket/pkg/storage"
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
)
type State int
const (
	AUTHORIZATION State = iota
	TRANSACTION
	QUIT
)
func (s State) String() string {
	switch s {
	case AUTHORIZATION:
		return "AUTHORIZATION"
	case TRANSACTION:
		return "TRANSACTION"
	case QUIT:
		return "QUIT"
	}
	return "Unknown"
}
var commands = map[string]bool{
	"QUIT": true,
	"STAT": true,
	"LIST": true,
	"RETR": true,
	"DELE": true,
	"NOOP": true,
	"RSET": true,
	"TOP":  true,
	"UIDL": true,
	"USER": true,
	"PASS": true,
	"APOP": true,
	"CAPA": true,
}
type Session struct {
	*Server                       
	id         int                
	conn       net.Conn           
	remoteHost string             
	sendError  error              
	state      State              
	reader     *bufio.Reader      
	user       string             
	messages   []storage.Message  
	retain     []bool             
	msgCount   int                
	logger     zerolog.Logger     
	debug      bool               
}
func NewSession(server *Server, id int, conn net.Conn, logger zerolog.Logger) *Session {
	reader := bufio.NewReader(conn)
	host, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
	return &Session{
		Server:     server,
		id:         id,
		conn:       conn,
		state:      AUTHORIZATION,
		reader:     reader,
		remoteHost: host,
		logger:     logger,
		debug:      server.config.Debug,
	}
}
func (s *Session) String() string {
	return fmt.Sprintf("Session{id: %v, state: %v}", s.id, s.state)
}
func (s *Server) startSession(id int, conn net.Conn) {
	logger := log.With().Str("module", "pop3").Str("remote", conn.RemoteAddr().String()).
		Int("session", id).Logger()
	logger.Info().Msg("Starting POP3 session")
	defer func() {
		if err := conn.Close(); err != nil {
			logger.Warn().Err(err).Msg("Closing connection")
		}
		s.wg.Done()
	}()
	ssn := NewSession(s, id, conn, logger)
	ssn.send(fmt.Sprintf("+OK Inbucket POP3 server ready <%v.%v@%v>", os.Getpid(),
		time.Now().Unix(), s.config.Domain))
	for ssn.state != QUIT && ssn.sendError == nil {
		line, err := ssn.readLine()
		if err == nil {
			if cmd, arg, ok := ssn.parseCmd(line); ok {
				if cmd == "" {
					ssn.send("-ERR Speak up")
					continue
				}
				if !commands[cmd] {
					ssn.send(fmt.Sprintf("-ERR Syntax error, %v command unrecognized", cmd))
					ssn.logger.Warn().Msgf("Unrecognized command: %v", cmd)
					continue
				}
				switch cmd {
				case "CAPA":
					ssn.send("+OK Capability list follows")
					ssn.send("TOP")
					ssn.send("USER")
					ssn.send("UIDL")
					ssn.send("IMPLEMENTATION Inbucket")
					ssn.send(".")
					continue
				}
				switch ssn.state {
				case AUTHORIZATION:
					ssn.authorizationHandler(cmd, arg)
					continue
				case TRANSACTION:
					ssn.transactionHandler(cmd, arg)
					continue
				}
				ssn.logger.Error().Msgf("Session entered unexpected state %v", ssn.state)
				break
			} else {
				ssn.send("-ERR Syntax error, command garbled")
			}
		} else {
			if err == io.EOF {
				switch ssn.state {
				case AUTHORIZATION:
					ssn.logger.Info().Msgf("Client closed connection (state %v)", ssn.state)
				default:
					ssn.logger.Warn().Msgf("Got EOF while in state %v", ssn.state)
				}
				break
			}
			ssn.logger.Warn().Msgf("Connection error: %v", err)
			if netErr, ok := err.(net.Error); ok {
				if netErr.Timeout() {
					ssn.send("-ERR Idle timeout, bye bye")
					break
				}
			}
			ssn.send("-ERR Connection error, sorry")
			break
		}
	}
	if ssn.sendError != nil {
		ssn.logger.Warn().Msgf("Network send error: %v", ssn.sendError)
	}
	ssn.logger.Info().Msgf("Closing connection")
}
func (s *Session) authorizationHandler(cmd string, args []string) {
	switch cmd {
	case "QUIT":
		s.send("+OK Goodnight and good luck")
		s.enterState(QUIT)
	case "USER":
		if len(args) > 0 {
			s.user = args[0]
			s.send(fmt.Sprintf("+OK Hello %v, welcome to Inbucket", s.user))
		} else {
			s.send("-ERR Missing username argument")
		}
	case "PASS":
		if s.user == "" {
			s.ooSeq(cmd)
		} else {
			s.loadMailbox()
			s.send(fmt.Sprintf("+OK Found %v messages for %v", s.msgCount, s.user))
			s.enterState(TRANSACTION)
		}
	case "APOP":
		if len(args) != 2 {
			s.logger.Warn().Msgf("Expected two arguments for APOP")
			s.send("-ERR APOP requires two arguments")
			return
		}
		s.user = args[0]
		s.loadMailbox()
		s.send(fmt.Sprintf("+OK Found %v messages for %v", s.msgCount, s.user))
		s.enterState(TRANSACTION)
	default:
		s.ooSeq(cmd)
	}
}
func (s *Session) transactionHandler(cmd string, args []string) {
	switch cmd {
	case "STAT":
		if len(args) != 0 {
			s.logger.Warn().Msgf("STAT got an unexpected argument")
			s.send("-ERR STAT command must have no arguments")
			return
		}
		var count int
		var size int64
		for i, msg := range s.messages {
			if s.retain[i] {
				count++
				size += msg.Size()
			}
		}
		s.send(fmt.Sprintf("+OK %v %v", count, size))
	case "LIST":
		if len(args) > 1 {
			s.logger.Warn().Msgf("LIST command had more than 1 argument")
			s.send("-ERR LIST command must have zero or one argument")
			return
		}
		if len(args) == 1 {
			msgNum, err := strconv.ParseInt(args[0], 10, 32)
			if err != nil {
				s.logger.Warn().Msgf("LIST command argument was not an integer")
				s.send("-ERR LIST command requires an integer argument")
				return
			}
			if msgNum < 1 {
				s.logger.Warn().Msgf("LIST command argument was less than 1")
				s.send("-ERR LIST argument must be greater than 0")
				return
			}
			if int(msgNum) > len(s.messages) {
				s.logger.Warn().Msgf("LIST command argument was greater than number of messages")
				s.send("-ERR LIST argument must not exceed the number of messages")
				return
			}
			if !s.retain[msgNum-1] {
				s.logger.Warn().Msgf("Client tried to LIST a message it had deleted")
				s.send(fmt.Sprintf("-ERR You deleted message %v", msgNum))
				return
			}
			s.send(fmt.Sprintf("+OK %v %v", msgNum, s.messages[msgNum-1].Size()))
		} else {
			s.send(fmt.Sprintf("+OK Listing %v messages", s.msgCount))
			for i, msg := range s.messages {
				if s.retain[i] {
					s.send(fmt.Sprintf("%v %v", i+1, msg.Size()))
				}
			}
			s.send(".")
		}
	case "UIDL":
		if len(args) > 1 {
			s.logger.Warn().Msgf("UIDL command had more than 1 argument")
			s.send("-ERR UIDL command must have zero or one argument")
			return
		}
		if len(args) == 1 {
			msgNum, err := strconv.ParseInt(args[0], 10, 32)
			if err != nil {
				s.logger.Warn().Msgf("UIDL command argument was not an integer")
				s.send("-ERR UIDL command requires an integer argument")
				return
			}
			if msgNum < 1 {
				s.logger.Warn().Msgf("UIDL command argument was less than 1")
				s.send("-ERR UIDL argument must be greater than 0")
				return
			}
			if int(msgNum) > len(s.messages) {
				s.logger.Warn().Msgf("UIDL command argument was greater than number of messages")
				s.send("-ERR UIDL argument must not exceed the number of messages")
				return
			}
			if !s.retain[msgNum-1] {
				s.logger.Warn().Msgf("Client tried to UIDL a message it had deleted")
				s.send(fmt.Sprintf("-ERR You deleted message %v", msgNum))
				return
			}
			s.send(fmt.Sprintf("+OK %v %v", msgNum, s.messages[msgNum-1].ID()))
		} else {
			s.send(fmt.Sprintf("+OK Listing %v messages", s.msgCount))
			for i, msg := range s.messages {
				if s.retain[i] {
					s.send(fmt.Sprintf("%v %v", i+1, msg.ID()))
				}
			}
			s.send(".")
		}
	case "DELE":
		if len(args) != 1 {
			s.logger.Warn().Msgf("DELE command had invalid number of arguments")
			s.send("-ERR DELE command requires a single argument")
			return
		}
		msgNum, err := strconv.ParseInt(args[0], 10, 32)
		if err != nil {
			s.logger.Warn().Msgf("DELE command argument was not an integer")
			s.send("-ERR DELE command requires an integer argument")
			return
		}
		if msgNum < 1 {
			s.logger.Warn().Msgf("DELE command argument was less than 1")
			s.send("-ERR DELE argument must be greater than 0")
			return
		}
		if int(msgNum) > len(s.messages) {
			s.logger.Warn().Msgf("DELE command argument was greater than number of messages")
			s.send("-ERR DELE argument must not exceed the number of messages")
			return
		}
		if s.retain[msgNum-1] {
			s.retain[msgNum-1] = false
			s.msgCount--
			s.send(fmt.Sprintf("+OK Deleted message %v", msgNum))
		} else {
			s.logger.Warn().Msgf("Client tried to DELE an already deleted message")
			s.send(fmt.Sprintf("-ERR Message %v has already been deleted", msgNum))
		}
	case "RETR":
		if len(args) != 1 {
			s.logger.Warn().Msgf("RETR command had invalid number of arguments")
			s.send("-ERR RETR command requires a single argument")
			return
		}
		msgNum, err := strconv.ParseInt(args[0], 10, 32)
		if err != nil {
			s.logger.Warn().Msgf("RETR command argument was not an integer")
			s.send("-ERR RETR command requires an integer argument")
			return
		}
		if msgNum < 1 {
			s.logger.Warn().Msgf("RETR command argument was less than 1")
			s.send("-ERR RETR argument must be greater than 0")
			return
		}
		if int(msgNum) > len(s.messages) {
			s.logger.Warn().Msgf("RETR command argument was greater than number of messages")
			s.send("-ERR RETR argument must not exceed the number of messages")
			return
		}
		s.send(fmt.Sprintf("+OK %v bytes follows", s.messages[msgNum-1].Size()))
		s.sendMessage(s.messages[msgNum-1])
	case "TOP":
		if len(args) != 2 {
			s.logger.Warn().Msgf("TOP command had invalid number of arguments")
			s.send("-ERR TOP command requires two arguments")
			return
		}
		msgNum, err := strconv.ParseInt(args[0], 10, 32)
		if err != nil {
			s.logger.Warn().Msgf("TOP command first argument was not an integer")
			s.send("-ERR TOP command requires an integer argument")
			return
		}
		if msgNum < 1 {
			s.logger.Warn().Msgf("TOP command first argument was less than 1")
			s.send("-ERR TOP first argument must be greater than 0")
			return
		}
		if int(msgNum) > len(s.messages) {
			s.logger.Warn().Msgf("TOP command first argument was greater than number of messages")
			s.send("-ERR TOP first argument must not exceed the number of messages")
			return
		}
		var lines int64
		lines, err = strconv.ParseInt(args[1], 10, 32)
		if err != nil {
			s.logger.Warn().Msgf("TOP command second argument was not an integer")
			s.send("-ERR TOP command requires an integer argument")
			return
		}
		if lines < 0 {
			s.logger.Warn().Msgf("TOP command second argument was negative")
			s.send("-ERR TOP second argument must be non-negative")
			return
		}
		s.send("+OK Top of message follows")
		s.sendMessageTop(s.messages[msgNum-1], int(lines))
	case "QUIT":
		s.send("+OK We will process your deletes")
		s.processDeletes()
		s.enterState(QUIT)
	case "NOOP":
		s.send("+OK I have sucessfully done nothing")
	case "RSET":
		s.logger.Debug().Msgf("Resetting session state on RSET request")
		s.reset()
		s.send("+OK Session reset")
	default:
		s.ooSeq(cmd)
	}
}
func (s *Session) sendMessage(msg storage.Message) {
	reader, err := msg.Source()
	if err != nil {
		s.logger.Error().Msgf("Failed to read message for RETR command")
		s.send("-ERR Failed to RETR that message, internal error")
		return
	}
	defer func() {
		if err := reader.Close(); err != nil {
			s.logger.Error().Msgf("Failed to close message: %v", err)
		}
	}()
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, ".") {
			line = "." + line
		}
		s.send(line)
	}
	if err = scanner.Err(); err != nil {
		s.logger.Error().Msgf("Failed to read message for RETR command")
		s.send(".")
		s.send("-ERR Failed to RETR that message, internal error")
		return
	}
	s.send(".")
}
func (s *Session) sendMessageTop(msg storage.Message, lineCount int) {
	reader, err := msg.Source()
	if err != nil {
		s.logger.Error().Msgf("Failed to read message for RETR command")
		s.send("-ERR Failed to RETR that message, internal error")
		return
	}
	defer func() {
		if err := reader.Close(); err != nil {
			s.logger.Error().Msgf("Failed to close message: %v", err)
		}
	}()
	scanner := bufio.NewScanner(reader)
	inBody := false
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, ".") {
			line = "." + line
		}
		if inBody {
			if lineCount < 1 {
				break
			} else {
				lineCount--
			}
		} else {
			if line == "" {
				inBody = true
			}
		}
		s.send(line)
	}
	if err = scanner.Err(); err != nil {
		s.logger.Error().Msgf("Failed to read message for RETR command")
		s.send(".")
		s.send("-ERR Failed to RETR that message, internal error")
		return
	}
	s.send(".")
}