* chore: Init AGENTS.md Signed-off-by: James Hillyerd <james@hillyerd.com> * chore: Central go-version for GitHub actions Signed-off-by: James Hillyerd <james@hillyerd.com> --------- Signed-off-by: James Hillyerd <james@hillyerd.com>
8.0 KiB
AGENTS.md - Inbucket
Guidance for AI agents working in this codebase.
Project Overview
Inbucket is an email testing service that accepts messages for any email address and makes them available via web, REST, and POP3 interfaces. It's a self-contained Go application with an Elm-based web UI.
Tech Stack:
- Backend: Go 1.25+
- Frontend: Elm 0.19.1 with Parcel bundler
- Logging: zerolog
- Testing: testify (assert/require/suite), goldiff for golden file tests
- HTTP Router: gorilla/mux
- Configuration: envconfig (environment variables)
- Optional: Lua scripting for extensions (gopher-lua)
Essential Commands
Build
# Build Go binaries (inbucket server + client CLI)
make build
# Or build directly
go build ./cmd/inbucket
go build ./cmd/client
# Build UI (required before running server)
cd ui && yarn install && yarn build
Test
# Run all Go tests with race detection
make test
# or
go test -race ./...
# Run tests for a specific package
go test -race ./pkg/storage/...
# Run tests with coverage
go test -race -coverprofile=profile.cov ./...
Lint
# CI uses golangci-lint
golangci-lint run
# Make's lint target (older, uses golint)
make lint
Run Development Server
# Build everything first
make build
cd ui && yarn build && cd ..
# Run with dev config
./etc/dev-start.sh
# Or run directly with defaults
./inbucket
Default ports:
- Web UI: http://localhost:9000
- SMTP: localhost:2500
- POP3: localhost:1100
UI Development
cd ui
# Install dependencies
yarn install
# Development server with HMR (proxies to Go backend)
yarn start
# Production build
yarn build
# Clean build artifacts
yarn clean
Code Organization
cmd/
inbucket/ # Main server binary
client/ # CLI client for REST API
pkg/
config/ # Environment-based configuration
extension/ # Lua extension system
luahost/ # Lua VM pool and bindings
event/ # Extension event types
message/ # Message manager (storage abstraction)
metric/ # Expvar metrics
msghub/ # Real-time message pub/sub
policy/ # Email address/domain policies
rest/ # REST API v1/v2 controllers
client/ # Go client library for REST API
model/ # JSON API models
server/
smtp/ # SMTP server
pop3/ # POP3 server
web/ # HTTP server, handlers, helpers
storage/ # Storage interface and implementations
file/ # File-based storage
mem/ # In-memory storage
stringutil/ # String utilities
test/ # Test utilities and integration tests
webui/ # Web UI controllers
ui/
src/
Main.elm # Elm app entry point
Api.elm # API client
Page/ # Page modules (Home, Mailbox, Monitor, Status)
Data/ # Data models
tests/ # Elm tests
Configuration
Inbucket uses environment variables for all configuration. Key variables:
| Variable | Default | Description |
|---|---|---|
INBUCKET_LOGLEVEL |
info |
debug, info, warn, error |
INBUCKET_MAILBOXNAMING |
local |
local, full, or domain |
INBUCKET_SMTP_ADDR |
0.0.0.0:2500 |
SMTP listen address |
INBUCKET_WEB_ADDR |
0.0.0.0:9000 |
HTTP listen address |
INBUCKET_POP3_ADDR |
0.0.0.0:1100 |
POP3 listen address |
INBUCKET_STORAGE_TYPE |
memory |
memory or file |
INBUCKET_WEB_UIDIR |
ui/dist |
Path to built UI files |
Run ./inbucket -help for complete list.
See doc/config.md for detailed documentation.
Code Patterns
Error Handling
- Use zerolog for structured logging
- Return errors up the call stack; log at the top level
- Use
github.com/pkg/errorspatterns for wrapping
HTTP Handlers
Handlers follow this pattern in pkg/server/web/:
func Handler(f func(http.ResponseWriter, *http.Request, *Context) error) http.Handler
Controllers return errors; the wrapper handles HTTP responses.
Storage Interface
New storage backends implement storage.Store interface (pkg/storage/storage.go):
type Store interface {
AddMessage(message Message) (id string, err error)
GetMessage(mailbox, id string) (Message, error)
GetMessages(mailbox string) ([]Message, error)
MarkSeen(mailbox, id string) error
PurgeMessages(mailbox string) error
RemoveMessage(mailbox, id string) error
VisitMailboxes(f func([]Message) (cont bool)) error
}
Register in cmd/inbucket/main.go init():
storage.Constructors["mytype"] = mystore.New
JSON Tag Convention
JSON fields use kebab-case (configured in .golangci.yml tagliatelle):
type Example struct {
FieldName string `json:"field-name"`
}
Elm Architecture
The UI follows The Elm Architecture:
Main.elm- App shell, routingPage/*.elm- Page modules with Model, Msg, init, update, viewData/*.elm- Data types and JSON decodersApi.elm- HTTP client for REST API
Testing
Test Structure
- Unit tests: alongside source files (
*_test.go) - Integration tests:
pkg/test/integration_test.go - Test utilities:
pkg/test/
Test Frameworks
- Standard
testingpackage github.com/stretchr/testify/assert- assertionsgithub.com/stretchr/testify/require- fatal assertionsgithub.com/stretchr/testify/suite- test suitesgithub.com/jhillyerd/goldiff- golden file testing
Test Utilities
Located in pkg/test/:
StoreStub,ManagerStub- mock implementationsDeliverToStore()- create test messagesStoreSuite()- table-driven storage testsNewLuaState()- Lua testing helper
Golden File Tests
Input in pkg/test/testdata/*.txt, expected output in *.golden:
goldiff.File(t, got, "testdata", "basic.golden")
Running Specific Tests
# Run tests matching pattern
go test -race -run TestIntegration ./pkg/test/
# Run with verbose output
go test -race -v ./pkg/storage/mem/
# Run storage suite for specific implementation
go test -race -run TestMemStore ./pkg/storage/mem/
CI/CD
GitHub Actions workflows in .github/workflows/:
build-and-test.yml- Build and test on Linux/Windows, coverage to coverallslint.yml- golangci-lintdocker-build.yml- Docker image buildsrelease.yml- goreleaser for releases
Important Gotchas
-
UI must be built before running server - The Go server serves static files from
ui/dist/ -
Storage type affects persistence -
memorystorage loses all data on restart; usefilefor persistence -
Port conflicts - Default ports (9000, 2500, 1100) may conflict with other services
-
Lua scripting is optional - If
inbucket.luais not present, the server runs without extensions -
Test coverage requires race detector - CI always runs with
-race -
golangci-lint v2 config - Uses v2 format in
.golangci.yml -
Windows paths in storage - Use
$instead of:in file storage paths (e.g.,D$/inbucket)
REST API
Base URL: http://localhost:9000/api/
API v1 Endpoints
GET /v1/mailbox/{name}- List messagesGET /v1/mailbox/{name}/{id}- Get messagePATCH /v1/mailbox/{name}/{id}- Mark as seenDELETE /v1/mailbox/{name}- Purge mailboxDELETE /v1/mailbox/{name}/{id}- Delete messageGET /v1/mailbox/{name}/{id}/source- Get raw source
API v2 Endpoints
GET /v2/monitor/messages- WebSocket for real-time messages
Go client available: github.com/inbucket/inbucket/v3/pkg/rest/client
Development Tips
-
Quick iteration - Use
make reflexfor auto-rebuild on Go file changes -
UI development - Run
yarn startinui/for HMR; it proxies API requests to the Go server -
Debug network - Run with
-netdebugflag to dump SMTP/POP3 traffic -
Test email sending - Use swaks or the test scripts in
etc/swaks-tests/ -
Check configuration - Run
./inbucket -helpto see all env vars and defaults