diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 728c61d..7bc55bd 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -9,11 +9,7 @@ on: jobs: linux-go-build: runs-on: ubuntu-latest - name: Linux Go ${{ matrix.go }} build - strategy: - matrix: - go: - - '1.25' + name: Linux Go build steps: - uses: actions/checkout@v6 with: @@ -21,7 +17,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v6 with: - go-version: ${{ matrix.go }} + go-version-file: '.go-version' check-latest: true - name: Build and test run: | @@ -31,7 +27,7 @@ jobs: uses: shogo82148/actions-goveralls@v1 with: path-to-profile: profile.cov - flag-name: Linux-Go-${{ matrix.go }} + flag-name: Linux-Go parallel: true windows-go-build: @@ -44,7 +40,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v6 with: - go-version: '1.25' + go-version-file: '.go-version' - name: Build run: go build ./... - name: Test diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8068788..df7fe37 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: - go-version: '1.25' + go-version-file: '.go-version' - name: golangci-lint uses: golangci/golangci-lint-action@v9 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 654d36c..2462a93 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v6 with: - go-version: '1.25' + go-version-file: '.go-version' check-latest: true - name: Setup Node.js diff --git a/.go-version b/.go-version new file mode 100644 index 0000000..5e2b950 --- /dev/null +++ b/.go-version @@ -0,0 +1 @@ +1.25 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0d98fd9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,293 @@ +# 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 + +```bash +# 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 + +```bash +# 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 + +```bash +# CI uses golangci-lint +golangci-lint run + +# Make's lint target (older, uses golint) +make lint +``` + +### Run Development Server + +```bash +# 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 + +```bash +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/errors` patterns for wrapping + +### HTTP Handlers +Handlers follow this pattern in `pkg/server/web/`: +```go +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`): +```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(): +```go +storage.Constructors["mytype"] = mystore.New +``` + +### JSON Tag Convention +JSON fields use kebab-case (configured in `.golangci.yml` tagliatelle): +```go +type Example struct { + FieldName string `json:"field-name"` +} +``` + +### Elm Architecture +The UI follows The Elm Architecture: +- `Main.elm` - App shell, routing +- `Page/*.elm` - Page modules with Model, Msg, init, update, view +- `Data/*.elm` - Data types and JSON decoders +- `Api.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 `testing` package +- `github.com/stretchr/testify/assert` - assertions +- `github.com/stretchr/testify/require` - fatal assertions +- `github.com/stretchr/testify/suite` - test suites +- `github.com/jhillyerd/goldiff` - golden file testing + +### Test Utilities +Located in `pkg/test/`: +- `StoreStub`, `ManagerStub` - mock implementations +- `DeliverToStore()` - create test messages +- `StoreSuite()` - table-driven storage tests +- `NewLuaState()` - Lua testing helper + +### Golden File Tests +Input in `pkg/test/testdata/*.txt`, expected output in `*.golden`: +```go +goldiff.File(t, got, "testdata", "basic.golden") +``` + +### Running Specific Tests +```bash +# 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 coveralls +- `lint.yml` - golangci-lint +- `docker-build.yml` - Docker image builds +- `release.yml` - goreleaser for releases + +## Important Gotchas + +1. **UI must be built before running server** - The Go server serves static files from `ui/dist/` + +2. **Storage type affects persistence** - `memory` storage loses all data on restart; use `file` for persistence + +3. **Port conflicts** - Default ports (9000, 2500, 1100) may conflict with other services + +4. **Lua scripting is optional** - If `inbucket.lua` is not present, the server runs without extensions + +5. **Test coverage requires race detector** - CI always runs with `-race` + +6. **golangci-lint v2 config** - Uses v2 format in `.golangci.yml` + +7. **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 messages +- `GET /v1/mailbox/{name}/{id}` - Get message +- `PATCH /v1/mailbox/{name}/{id}` - Mark as seen +- `DELETE /v1/mailbox/{name}` - Purge mailbox +- `DELETE /v1/mailbox/{name}/{id}` - Delete message +- `GET /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 + +1. **Quick iteration** - Use `make reflex` for auto-rebuild on Go file changes + +2. **UI development** - Run `yarn start` in `ui/` for HMR; it proxies API requests to the Go server + +3. **Debug network** - Run with `-netdebug` flag to dump SMTP/POP3 traffic + +4. **Test email sending** - Use swaks or the test scripts in `etc/swaks-tests/` + +5. **Check configuration** - Run `./inbucket -help` to see all env vars and defaults