smtpx

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Jun 13, 2025 License: MIT Imports: 18 Imported by: 2

README

SMTPX

A lightweight SMTP server package written in Go that feels familiar.

Installation

go get github.com/modfin/smtpx

Usage

One idea for smtpx is to have it feel familiar and have the same feel as the standard library has for net/http.


package main

import (
	"context"
	"crypto/tls"
	"fmt"
	"github.com/modfin/smtpx"
	"github.com/modfin/smtpx/envelope"
	"github.com/modfin/smtpx/middleware"
	"github.com/modfin/smtpx/responses"
	"golang.org/x/crypto/acme/autocert"
	"log/slog"
	"os"
	"os/signal"
	"time"
)

func main() {

	hostname := "example.com"
	certManager := &autocert.Manager{
		Prompt:     autocert.AcceptTOS,
		Cache:      autocert.DirCache("acme-cache"),
		HostPolicy: autocert.HostWhitelist(hostname),
	}

	slog.SetLogLoggerLevel(slog.LevelDebug)

	server := smtpx.Server{
		Logger: slog.Default(),

		// Hostname is used for HELO command, and is required for TLS
		Hostname: hostname,
		Addr:     ":25",

		// TLSConfig is optional and is used for STARTTLS command
		TLSConfig: &tls.Config{ // example using autocert for TLS
			GetCertificate: certManager.GetCertificate,
		},

		// Middlewares are executed in order
		Middlewares: []smtpx.Middleware{
			middleware.Logger(slog.Default()),
			middleware.Recover,
			middleware.AddReturnPath,
		},

		// Handler for incoming emails
		// smtpx.NewHandler creates a handler from a function
		Handler: smtpx.NewHandler(func(e *envelope.Envelope) smtpx.Response {

			fmt.Println("Command MAIL", e.MailFrom)
			fmt.Println("Command RCPT", e.RcptTo)
			fmt.Println("Command DATA", e.Data.String())

			// Opening the envelope and getting the mail
			// ie. Splitting the Data section of a email into header and body
			mail, err := e.Mail()
			if err != nil {
				return responses.FailSyntaxError
			}

			// Parses headers while keeping encoding.
			// For human-readable decoded format, use mail.Headers()
			headers, err := mail.HeadersLiteral()

			// Error handling ignored for brevity
			fmt.Println("From:", headers.From())
			fmt.Println("To:", headers.To())
			fmt.Println("Subject:", headers.Get("Subject"))
			fmt.Println("Body:", string(mail.Body()))

			return smtpx.NewResponse(250, "OK")
		}),
	}

	// Graceful shutdown
	go func() {
		sig := make(chan os.Signal, 1)
		signal.Notify(sig, os.Interrupt, os.Kill)
		<-sig

		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()

		err := server.Shutdown(ctx)
		if err != nil {
			slog.Default().Error("failed to shutdown server", "err", err)
		}
	}()

	if err := server.ListenAndServe(); err != nil {
		slog.Default().Error("failed to start server", "err", err)
	}
}



Tribute

A Fork

smtpx started out as a fork of go-guerrilla but there is very little left from the original project. The goal is quite different for smtpx, and pretty much everything has been rewritten in a more go-like way focused on have smtpx work as a package and not a stand-alone server.

https://github.com/phires/go-guerrilla (original https://github.com/flashmob/go-guerrilla)

Other projects

There are a few other smtp servers written in go, so please have look before using smtpx.

License

MIT

Documentation

Index

Constants

View Source
const (
	// ConnGreeting The connection has connected, and is awaiting our first response
	ConnGreeting = iota
	// ConnCmd We have responded to the connection's connection and are awaiting a command
	ConnCmd
	// ConnData We have received the sender and recipient information
	ConnData
	// ConnStartTLS We have agreed with the connection to secure the connection over TLS
	ConnStartTLS
	// Server will shut down, connection to shutdown on next command turn
	ConnShutdown
)
View Source
const (
	Name    = "Brevx"
	Version = "0.0.1"
)
View Source
const (
	// Server has just been created
	ServerStateNew = iota
	// Server has just been stopped
	ServerStateStopped
	// Server has been started and is running
	ServerStateRunning
	// Server could not start due to an error
	ServerStateStartError
)
View Source
const CharsetDefault = "default"
View Source
const CharsetUtf8 = "SMTPUTF8"
View Source
const CommandLineMaxLength = 1024
View Source
const CommandVerbMaxLength = 16

Variables

This section is empty.

Functions

func NewSMTPReader

func NewSMTPReader(r io.Reader, n int64) *smtpReader

NewSMTPReader setup a new smtpReader with n byte read limit

Types

type ClientState

type ClientState int

ClientState indicates which part of the SMTP transaction a given connection is in.

type Handler

type Handler interface {
	// Data processes then saves the mail envelope
	// Success should return nil or responses.SuccessMessageQueued
	Data(*envelope.Envelope) Response
}

Handler is process received mail. Depending on the implementation, they can store mail in the database, write to a file, check for spam, re-transmit to another Server, etc.

Returning nil will translate to 250 OK Response Returning a non 2xx Response will abort the transaction

var NoopBackend Handler = NewHandler(func(e *envelope.Envelope) Response {
	return nil
})

func NewHandler

func NewHandler(handler HandlerFunc) Handler

type HandlerFunc

type HandlerFunc func(*envelope.Envelope) Response

type Middleware

type Middleware func(next HandlerFunc) HandlerFunc

type Response

type Response interface {
	fmt.Stringer
	StatusCode() int
	Class() int
}

Response represents a response to an SMTP connection after receiving DATA. The String method should return an SMTP message ready to send back to the connection, for example `250 OK: Message received`.

func NewResponse

func NewResponse(code int, comment string) Response

type ResponseErr

type ResponseErr interface {
	Response
	error
}

func WrapResponse

func WrapResponse(res Response, err error) ResponseErr

type SMTPReaderError

type SMTPReaderError string
const LimitError SMTPReaderError = "read limit reached"

func (SMTPReaderError) Error

func (e SMTPReaderError) Error() string

type Server

type Server struct {
	Logger *slog.Logger

	// Middlewares will be run in the order they are specified before backend is called
	// m1 -> m2 -> Handler
	//               V
	// m1 <- m2 <- Handler
	Middlewares []Middleware

	// Handler will be receiving envelopes after the Data command
	Handler Handler

	// TLSConfig will be used when TLS is enabled
	TLSConfig *tls.Config
	// AlwaysOn run this Server as a pure TLS Server, i.e. SMTPS
	TLSAlwaysOn bool

	// Hostname will be used in the Server's reply to HELO/EHLO. If TLS enabled
	// make sure that the Hostname matches the cert. Defaults to os.Hostname()
	// Hostname will also be used to fill the 'Host' property when the "RCPT TO" address is
	// addressed to just <postmaster>
	Hostname string

	// Addr is the interface specified in <ip>:<port> - defaults to ":25"
	Addr string

	// MaxSize is the maximum size of an email that will be accepted for delivery.
	// Defaults to 10 Mebibytes
	MaxSize int64

	// Timeout specifies the connection timeout in seconds. Defaults to 30
	// TODO: Implment this
	Timeout int

	// MaxClients controls how many maximum clients we can handle at once.
	// Defaults to defaultMaxClients
	// TODO: Implment this
	MaxClients int

	// XClientOn when using a proxy such as Nginx, XCLIENT command is used to pass the
	// original connection's IP address & connection's HELO
	XClientOn bool
	ProxyOn   bool

	// MaxRecipients is the maximum number of recipients allowed in a single RCPT command
	// Defaults to defaultMaxRecipients = 100
	MaxRecipients int

	// MaxUnrecognizedCommands is the maximum number of unrecognized commands allowed before the server terminates
	// the connection, defaults to defaultMaxUnrecognizedCommands = 5
	MaxUnrecognizedCommands int
	// contains filtered or unexported fields
}

Server listens for SMTP clients on the port specified in its config

func (*Server) GetActiveClientsCount

func (s *Server) GetActiveClientsCount() int

func (*Server) ListenAndServe

func (s *Server) ListenAndServe() error

ListenAndServe begin accepting SMTP clients. Will block unless there is an error or Server.Shutdown() is called

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

func (*Server) Use

func (s *Server) Use(middleware ...Middleware)

Directories

Path Synopsis
example module
authres
Package authres parses and formats Authentication-Results
Package authres parses and formats Authentication-Results
authres/dkim
Package dkim creates and verifies DKIM signatures, as specified in RFC 6376.
Package dkim creates and verifies DKIM signatures, as specified in RFC 6376.
authres/dmarc
Package dmarc implements DMARC as specified in RFC 7489.
Package dmarc implements DMARC as specified in RFC 7489.

Jump to

Keyboard shortcuts

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