Package dependency graph for github.com/aftersmtp/aftersmtp, derived from actual
Go imports (go list). Only intra-module edges are shown; standard-library and
third-party imports are omitted for readability but noted where they shape the
architecture. Regenerate with the command in Regenerating.
Last reconciled: 2026-07-31. The Go toolchain was unavailable locally, so this is a
static import review rather than a fresh go list render.
Internal package dependencies
flowchart TD
%% Entry points
subgraph entry[Entry points]
CMD[cmd/aftersmtp]
CLI[cmd/aftersmtp-cli]
end
%% Protocol surface
subgraph proto[Protocol]
AMP[protocol/amp]
PCLIENT[protocol/client]
LEGACY[protocol/legacy]
end
%% Message processing
subgraph proc[Processing]
PIPE[pipeline]
MS[policy/mailscript]
ROUTE[routing]
QUEUE[queue]
end
%% Identity / trust
subgraph trust[Identity, Trust & Ledger]
IDENT[identity]
TRUST[trust]
LEDGER[ledger]
end
%% Security
subgraph sec[Security]
SECURITY[security]
DANE[security/dane]
DNS[dns]
end
%% Primitives / infra
subgraph base[Primitives & Infra]
CRYPTO[crypto]
STORAGE[storage]
ROOM[room]
TELEM[telemetry]
BUFPOOL[bufpool]
CONFIG[config]
NOTIFY[notify]
end
%% --- Entry point edges ---
CMD --> CONFIG & CRYPTO & DNS & LEDGER & NOTIFY & PIPE & MS & AMP & PCLIENT & LEGACY & QUEUE & ROUTE & SECURITY & STORAGE & TELEM & TRUST
CLI --> CRYPTO & ROOM & STORAGE & TRUST
%% --- Protocol edges ---
AMP --> BUFPOOL & CRYPTO & LEDGER & TELEM
PCLIENT --> CRYPTO & IDENT & LEDGER & AMP & TELEM
LEGACY --> BUFPOOL & CRYPTO & LEDGER & AMP & ROUTE & SECURITY & DANE & TELEM
%% --- Processing edges ---
PIPE --> LEDGER & MS & AMP & QUEUE & ROOM & STORAGE & TELEM
MS --> LEDGER & AMP & ROUTE
ROUTE --> AMP & TELEM
QUEUE --> AMP & TELEM
%% --- Trust edges ---
IDENT --> CRYPTO & LEDGER & TELEM
LEDGER --> TELEM
%% --- Security edges ---
SECURITY --> DNS
%% --- Storage edges ---
STORAGE --> AMP
classDef hub fill:#ffe9c7,stroke:#d98a00,stroke-width:2px;
classDef leaf fill:#e7f5e7,stroke:#3a8a3a;
class AMP,LEDGER hub;
class CRYPTO,TELEM,BUFPOOL,CONFIG,ROOM,DNS leaf;
Legend: amber = high-fan-in hub (many packages depend on it — change with care); green = leaf with no intra-module dependencies.
Structural observations
These are about the shape of the dependency graph, independent of the line-level crypto review in CRYPTO_REVIEW.md.
-
No import cycles. The graph is a clean DAG; layering (entry → protocol → processing → trust/security → primitives) is respected. This is the single biggest thing that keeps a protocol stack maintainable, and it holds here.
-
protocol/ampis an over-broad hub. Nine internal packages import it, because it holds both the generated protobuf message types and the QUIC + gRPC server implementation (it pulls inquic-go,grpc,protobuf,prometheus,unsafe). That meansstorage,routing, andqueue— which only need theAMPMessagetype — transitively compile the entire QUIC/gRPC server. Recommend splitting the generated types into a leaf package (protocol/amp/amppborprotocol/amptypes) and keeping the server inprotocol/amp. Lowers build coupling and lets data-plane packages stay light. -
ledgeris a heavy dependency pulled in widely.identity,pipeline,policy/mailscript,protocol/{amp,client,legacy}all importledger, which drags incentrifuge/go-substrate-rpc-client. A failure or version bump in the Substrate client ripples across most of the stack. Consider an interface seam (ledger.Anchorer/ledger.Resolver) so callers depend on a small local interface, not the concrete Substrate client — easier testing and a clean swap point for the SQLite fallback. -
cryptois a clean leaf. No internal deps, only stdlib +hkdf+lru. Exactly what you want from the security-critical core: it can be audited and fuzzed in isolation. Keep it that way. -
trustis intentionally small. It owns durable consent/reputation/request state without importing transport or storage packages. Keep it transport-neutral so all ingress paths use the same admission decision. -
telemetryis a near-universal leaf. Fine and expected; just keep it from ever importing back "up" the stack (which would create cycles). -
securityvssecurity/dane.security(DKIM/ARC) depends ondns, whilesecurity/danecarries its ownmiekg/dnsresolver. Two DNS paths in one subsystem is worth consolidating — DANE needs DNSSEC-validating lookups that the plaindnspackage may not provide, but the split should be intentional and documented, not incidental.
Regenerating
go list -f '{{.ImportPath}}|{{join .Imports ";"}}' ./cmd/... ./internal/... \
| sed 's#github.com/aftersmtp/aftersmtp/##g'
Filter the right-hand side to paths beginning with internal/ or cmd/ to get the
intra-module edges rendered above.