TeaNode · Documentation · Source

Contributing

TeaNode is a mail server. Mail is unforgiving: a mistake does not throw an exception, it silently loses somebody's message or gets a domain's reputation burned. That shapes most of what follows.

Start with docs/reference/local-development.md to get a build and a database.

Before you send a change

make format        # gofmt
make lint          # golangci-lint, then the naming checks if you have them
make test          # starts a PostgreSQL container automatically
make build

CI runs make lint-ci, which is golangci-lint only. A local check enforces this project's naming conventions and is local-only, so a contributor without it is never blocked; run it anyway if you have it.

Naming

These are not negotiable, and make lint checks most of them.

The dashboard

The web dashboard has its own conventions — which shared component to reach for, where a panel's action goes, when a row action is an icon and when it is a word — in docs/coding/frontend-design.md. Read it before adding a page or a panel. A page that builds by hand what a component already does is the change most likely to be sent back.

Comments

Explain why, not what. The what is in the code underneath.

A comment earns its place when it records something the reader cannot recover from the code: a protocol requirement, a failure that was observed in production, a deliberate choice among several reasonable ones. // increment the counter above counter++ is noise. // Reject before the DATA command so a spammer pays for the connection is worth having.

Exported identifiers get a doc comment starting with their name.

Invariants

Break any of these and something downstream fails in a way that is hard to trace back.

Tests

Write the test that would have caught the bug. A test that only proves the code runs is not worth the maintenance.

Tests that need PostgreSQL take it from internal/db/dbtest, which skips when TEANODE_TEST_DATABASE_HOST is unset, so go test still works without Docker.

A unit test must not reach the network. This was learned the hard way: an early version of autoacme.Open started its renewal loop during construction, so merely building a manager in a test contacted Let's Encrypt production. Construction and starting are now separate for exactly this reason.

Commits

Write the message for somebody trying to understand this change a year from now with no memory of the conversation that produced it.

Changelog

User-visible changes get a changelog entry, and there are two places to put one. The Changelog block of the pull request description is where most of them belong: it is reviewed alongside the change it describes, and the release bot collects it. Writing straight into CHANGELOG.md under Unreleased is the other way, and is what somebody committing to main does. Internal refactoring that an operator cannot observe needs neither; say so in the description and label the pull request no changelog.

That entry is also what releases the change. Every push to main that leaves something under Unreleased is cut into a release: the version comes from the entries — Added or Removed means a new minor, anything else a new patch — and the tag it pushes is what builds the binaries and the container image. A push with nothing under Unreleased releases nothing, which is most pushes.

To see what would happen before pushing:

.github/scripts/cut-release.sh          # says the version, writes nothing
.github/scripts/changelog.sh 0.2.0      # the notes that version would carry

On a laptop that reads only CHANGELOG.md; it says so, and says why. Reading the pull requests takes GITHUB_REPOSITORY and a token, which the workflow passes it.

A major version is never inferred. Deciding that a change breaks whoever is running this is a judgment, so it is the "Major release" workflow, run by hand, with the word MAJOR typed into it.

Decisions

If your change makes a choice a future reader would question, write a decision record in docs/decisions/ — see the README there. Larger work gets a design document in docs/planning/active/ first, moved to done/ when it lands.

Reporting a security problem

Do not open a public issue for anything exploitable in mail handling, authentication or certificate issuance. SECURITY.md says where it goes: GitHub's private vulnerability reporting, on the Security tab.

Documentation

Getting started

Running it

Security

Contributing

Every document is also plain markdown, listed in /llms.txt.