pop3

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2026 License: MIT Imports: 9 Imported by: 0

README

pop3

CI Go Reference

A POP3 (RFC 1939) server engine for Go, with zero external dependencies (standard library only).

You supply a Backend that authenticates users and exposes each user's maildrop as a slice of neutral Message values; the Server speaks the wire protocol — USER/PASS, CAPA, STLS (STARTTLS), STAT, LIST, UIDL, RETR, TOP, DELE, RSET, NOOP and QUIT. The engine holds no assumptions about where mail lives: a Backend can be a database, a maildir, or a remote API.

RETR/TOP serve exactly the bytes Mailbox.Retrieve returns, with RFC 1939 dot-stuffing applied on the wire, and messages a client DELEs are removed only on QUIT.

Install

go get github.com/rest-mail/go-pop3

Usage

Implement Backend and Mailbox, then hand the server a listener config:

package main

import (
	"crypto/tls"

	"github.com/rest-mail/go-pop3"
)

// store is your mail store. Authenticate returns a Mailbox scoped to the user.
type store struct{ /* db handle, etc. */ }

func (s *store) Authenticate(user, pass string) (pop3.Mailbox, error) {
	// verify credentials, then return the user's maildrop view
	return &maildrop{ /* ... */ }, nil
}

type maildrop struct{ /* ... */ }

func (m *maildrop) Messages() ([]pop3.Message, error) {
	// oldest-first; UID must be stable, Size is the STAT/LIST octet count
	return []pop3.Message{
		{UID: "1001", Size: 4213, Seen: false},
		{UID: "1002", Size: 1198, Seen: true},
	}, nil
}

func (m *maildrop) Retrieve(uid string) ([]byte, error) { /* full RFC 5322 bytes */ return nil, nil }
func (m *maildrop) MarkSeen(uid string) error           { /* after RETR */ return nil }
func (m *maildrop) Delete(uid string) error             { /* on QUIT */ return nil }

func main() {
	cert, _ := tls.LoadX509KeyPair("cert.pem", "key.pem")
	tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}}

	// nil Limiter -> pop3.NopLimiter (no per-IP limits).
	srv := pop3.NewServer(&store{}, tlsConfig, nil)
	if err := srv.ListenAndServe(pop3.Ports{POP3: 110, POP3TLS: 995}); err != nil {
		panic(err)
	}
	select {} // serve until Shutdown
}

For a single accepted connection (e.g. behind your own listener), construct a session directly with pop3.NewSession(conn, backend, tlsConfig, limiter) and call Handle().

Rate limiting

NewServer accepts a Limiter — a small structural interface (Accept/Release/RecordAuthFail/IsBanned/ResetAuth) the engine consults for per-IP connection caps and auth-failure bans. Pass nil (or pop3.NopLimiter{}) for none, or wire in your own; any type with those methods satisfies it.

License

MIT © 2026 rest-mail

Documentation

Overview

Package pop3 implements a POP3 (RFC 1939) server engine with zero external dependencies (standard library only).

A caller supplies a Backend that authenticates users and exposes each user's maildrop as a slice of neutral Message values; the Server speaks the wire protocol — USER/PASS, CAPA, STLS (STARTTLS), STAT, LIST, UIDL, RETR, TOP, DELE, RSET, NOOP and QUIT — and calls back into the Backend to fetch, mark-seen and delete messages. The engine holds no assumptions about where mail is stored: a Backend can be a database, a filesystem, or a remote API.

The message body served by RETR/TOP is whatever Mailbox.Retrieve returns, byte-for-byte, with RFC 1939 dot-stuffing applied on the wire. Messages the client DELEtes are only removed on QUIT, matching RFC 1939 semantics.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Backend

type Backend interface {
	// Authenticate validates the USER/PASS credentials. Returning a non-nil error
	// rejects the login (the client sees "-ERR ...") and counts as an auth
	// failure against the [Limiter]. On success it returns the [Mailbox] the
	// session operates on.
	Authenticate(user, pass string) (Mailbox, error)
}

Backend authenticates POP3 users. A Server calls Authenticate once per session, after USER and PASS have both been received.

type Limiter

type Limiter interface {
	// Accept reports whether a new connection from ip may proceed, incrementing
	// the in-use count when it returns true.
	Accept(ip string) bool
	// Release decrements the in-use count for ip when a connection ends.
	Release(ip string)
	// RecordAuthFail records an authentication failure from ip.
	RecordAuthFail(ip string)
	// IsBanned reports whether ip is currently banned.
	IsBanned(ip string) bool
	// ResetAuth clears the recorded auth-failure history for ip after a success.
	ResetAuth(ip string)
}

Limiter is the per-IP connection and authentication guard the Server consults. It is a structural interface: any type with these methods satisfies it. Pass NopLimiter (or nil) to impose no limits.

type Mailbox

type Mailbox interface {
	// Messages returns the maildrop contents oldest-first. It is called once,
	// immediately after authentication.
	Messages() ([]Message, error)
	// Retrieve returns the full RFC 5322 bytes of the message with the given UID,
	// served verbatim (subject to dot-stuffing) by RETR and TOP.
	Retrieve(uid string) ([]byte, error)
	// MarkSeen flags a message read after a successful RETR. POP3 has no read
	// state of its own; implementations without one may return nil.
	MarkSeen(uid string) error
	// Delete permanently removes a message. The Server calls it on QUIT, once for
	// each message the client DELEted during the session.
	Delete(uid string) error
}

Mailbox is a single authenticated POP3 maildrop. Every method is scoped to the user that Backend.Authenticate accepted; a Mailbox is used by one session.

type Message

type Message struct {
	// UID is the persistent unique identifier a client sees via UIDL. The Server
	// also passes it back to [Mailbox.Retrieve], [Mailbox.MarkSeen] and
	// [Mailbox.Delete] to name this message. It must be unique and stable within
	// the maildrop for the session's lifetime.
	UID string
	// Size is the octet count reported by STAT and LIST (the RFC 1939 maildrop
	// listing size). It need not equal the exact length Retrieve returns.
	Size int
	// Seen reports whether the message is already marked read. RETR issues a
	// MarkSeen only for a message that was previously unseen; TOP never does.
	Seen bool
}

Message is one message in a POP3 maildrop, presented oldest-first. Its slice position (+1) is the POP3 message number the client uses for the session.

type NopLimiter

type NopLimiter struct{}

NopLimiter is a Limiter that imposes no limits: it accepts every connection and never bans. It is the default when a nil Limiter is passed to NewServer or NewSession.

func (NopLimiter) Accept

func (NopLimiter) Accept(string) bool

func (NopLimiter) IsBanned

func (NopLimiter) IsBanned(string) bool

func (NopLimiter) RecordAuthFail

func (NopLimiter) RecordAuthFail(string)

func (NopLimiter) Release

func (NopLimiter) Release(string)

func (NopLimiter) ResetAuth

func (NopLimiter) ResetAuth(string)

type Ports

type Ports struct {
	POP3    int // 110 (STARTTLS)
	POP3TLS int // 995 (implicit TLS)
}

Ports defines the ports for POP3 services.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server listens for POP3 connections and spawns a session handler per client.

func NewServer

func NewServer(backend Backend, tlsConfig *tls.Config, limiter Limiter) *Server

NewServer creates a POP3 Server backed by the given Backend. A non-nil tlsConfig enables STLS (STARTTLS) and implicit-TLS listeners. A nil limiter defaults to NopLimiter.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe(ports Ports) error

ListenAndServe starts POP3 listeners on the specified ports. A zero port is skipped. It returns once the listeners are open; connections are served in the background until Server.Shutdown.

func (*Server) Shutdown

func (s *Server) Shutdown()

Shutdown gracefully stops the server: it closes all listeners and waits for in-flight sessions to finish.

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session represents a single POP3 conversation with a client.

func NewSession

func NewSession(conn net.Conn, backend Backend, tlsConfig *tls.Config, limiter Limiter) *Session

NewSession creates a POP3 session over conn, authenticating against backend. A nil limiter defaults to NopLimiter. Call Session.Handle to run it.

func (*Session) Handle

func (s *Session) Handle()

Handle runs the POP3 state machine until the client disconnects or QUITs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL