internal/ — the inventory

What's in the box

The complete inventory: every domain package and what it does, real excerpts from the code you will read, and the security baseline underneath. Everything on this page is measured on the code a buyer receives.

Inventory

Everything a B2B SaaS needs, already written and tested

Not a scaffold that stops at the login screen. Every line below is working code with tests behind it, in the repository you buy.

internal/auth

Sign-in, done properly

Every flow a real product needs, with the failure modes handled: enumeration, replay, rate limits, session invalidation.

  • Email and password hashed with argon2id (64 MiB, 3 iterations)
  • Email verification, password reset, and a magic link that doubles as passwordless signup
  • Google, GitHub, Microsoft Entra ID, and any OIDC provider by discovery
  • TOTP two-factor: QR enrollment, ten single-use recovery codes, required on every sign-in path
  • Neutral answers everywhere — signup, login and reset never reveal whether an account exists
  • Rate limits per IP and per account; a password change ends every other session
  • One-time tokens stored as SHA-256 hashes, single-use, with a lifetime per purpose
  • OAuth never merges accounts silently: an email collision requires an explicit link

internal/org

Teams, roles and hard isolation

Multi-tenancy is a row-level rule enforced in middleware and re-checked by tests, not a convention you have to remember.

  • Organizations with owner, admin and member roles, enforced server-side
  • Email invitations: single-use tokens, valid seven days
  • Every business table carries organization_id; every query filters on it
  • Outsiders get a 404, never a 403 that would confirm the organization exists
  • The last owner cannot be demoted, removed, or leave without handing over
  • ORG_MODE=solo turns the same code into a B2C product with one personal organization per account
  • A Projects module ships as the template to copy for your own resources
  • Cross-tenant isolation is covered end to end, not asserted in prose

internal/billing

Payments that survive a duplicate webhook

Two providers behind one seam, and an idempotency story you could explain to an auditor.

  • Stripe subscriptions and one-time products behind one provider seam
  • Idempotent webhooks: refunds and chargebacks are handled, replays are no-ops
  • Subscriptions belong to the organization: Checkout, customer portal, plan changes, invoices
  • One-time payments share the same plan config and land in an org-scoped purchases table
  • Webhooks read the raw body, verify the signature, and answer fast
  • The event id and the business write commit in one transaction — a replay is a no-op
  • Objects are re-fetched from the provider API by id; payload data is never trusted
  • Plans are configuration, so the pricing page and the server can never disagree

internal/jobs · internal/email

Emails and background work that survive a redeploy

The queue is a table, not a goroutine holding your users' emails hostage until the next restart.

  • Durable job queue in the database: claim, run, reschedule, retry
  • Mailer with three backends: the server log in development, any SMTP relay, or Resend
  • 16 transactional templates, embedded in the binary, in French and English
  • Three attempts with exponential backoff; definitive failures are logged as dead letters
  • Emails render from the locale stored on the account — a job has no request headers to read
  • SPF, DKIM and DMARC setup written down, because deliverability is not optional

internal/storage

Uploads, sessions and error reporting

The operational surface a product grows into, with the same no-SDK, no-lock-in treatment.

  • Storage behind one interface: local disk in development, any S3-compatible service in production
  • The S3 client is ~250 readable lines of SigV4 over net/http, not an SDK
  • Files are served by the app, so buckets stay private and membership is checked on every download
  • Uploads are capped and sniffed server-side against an allowlist (SVG refused on purpose)
  • Users list the devices signed in to their account and revoke them one by one or all at once
  • Every Error-level log record reaches a pluggable reporter; SENTRY_DSN enables the bundled one

internal/admin

A back office, and the numbers behind it

Moderation and metrics for the person running the business, gated server-side by a single environment variable.

  • Super-admin console, restricted server-side by SUPER_ADMIN_EMAIL
  • MRR, ARPU, 30-day churn, signups and plan mix, computed from your own database
  • Amounts reported per currency instead of being summed into a fiction
  • User moderation: ban, delete, impersonate — impersonation lands in the audit log
  • Read-only organization list and a searchable audit log
  • No provider round-trip: the page loads instantly and still works when Stripe is down

web/

A Vue 3 app you would have written yourself

TypeScript, Composition API, Pinia, Tailwind — with the tedious parts already handled: CSRF, errors, toasts, dark mode, i18n.

  • Vue 3 and TypeScript, Vite, Pinia, Vue Router, Tailwind CSS v4
  • A typed fetch client that carries the CSRF token and surfaces API errors
  • UI kit: modal, toast host, paginated table, avatars, form alerts
  • Dark mode applied before first paint and kept across reloads
  • French and English on every view, with the language stored on the account
  • SEO built in: public pages prerendered to static HTML at build time — canonical, hreflang, per-language URLs and sitemap — readable by search engines and AI assistants, no JavaScript required (this very page is served that way)
  • Console screens: dashboard with onboarding checklist, projects, members, billing, account settings

Makefile

Two commands do the repetitive work

The kit generates the chain you would otherwise copy by hand, and documents the calls it made instead of leaving TODOs.

  • make setup asks a handful of questions and writes .env, CSRF key included
  • make scaffold generates a full org-scoped resource: migration, queries, handler, routes, store, view, tests
  • make seed fills a development database with users, an organization and projects
  • sqlc turns real SQL into typed Go — no ORM, no query builder, nothing to un-learn
  • A markdown blog rendered by Go outside the SPA, with per-page SEO, sitemap and robots.txt
  • DECISIONS.md records every ambiguous call; the codebase contains no TODO

Architecture

Read the part that usually hides

Two excerpts, copied from the repository. They are one rule seen from both ends: tenancy is decided in a single place, and every query honours it.

Repository
cmd/server/main.gointernal/  auth/        sign-in, tokens, OAuth, TOTP  org/         organizations, members, invitations  billing/     provider seam → stripe (webhooks, refunds)  jobs/        durable queue and workers  email/       mailer + embedded templates  storage/     local disk · S3-compatible  safehttp/    hardened outbound client  admin/       metrics, moderation, audit log  http/        chi router · handlers/ · middleware/  db/          sqlc queries, goose migrationsmigrations/    portable SQL, both enginesweb/           Vue 3 app, embedded in the binary
Twenty-four Go packages, one per concern. No layers to traverse before you reach the code that answers the request.
internal/db/queries/projects.sqlsql
-- name: ListOrgProjects :manySELECT * FROM projectsWHERE organization_id = $1ORDER BY created_at DESC; -- name: GetProject :oneSELECT * FROM projectsWHERE id = $1 AND organization_id = $2;
Queries are SQL files. sqlc compiles them into typed Go, and every business query filters by organization.
internal/http/middleware/org.gogo
// Non-members get a 404, indistinguishable from an org// that does not exist: ids never leak across tenants.m, err := o.Q.GetOrgMember(r.Context(), sqlcgen.GetOrgMemberParams{	OrganizationID: orgID, UserID: u.ID,})if errors.Is(err, sql.ErrNoRows) {	writeError(w, http.StatusNotFound, "not found")	return}
The other end of the rule: membership is resolved in middleware, and an outsider cannot tell an organization they may not see from one that does not exist.

Security

A checklist you can audit, not a badge

Security work is mostly a list of things that must be true. Here is the list, and every line has a test behind it.

  • Passwords

    argon2id at 64 MiB and 3 iterations, with a per-password salt. No pepper to lose, no legacy hash to migrate.

  • Sessions

    Server-side, stored in the database. Cookies are HttpOnly, SameSite=Lax and Secure in production.

  • CSRF

    Every state-changing route is protected, and the frontend client carries the token for you.

  • Rate limiting

    Per IP and per account on sign-in, signup, password reset and the contact form.

  • No enumeration

    Signup, login and reset answer the same whether or not the address is known.

  • Tenant isolation

    Row-level filtering on every query, a 404 for outsiders, and end-to-end tests that try to cross the line.

  • Server-side RBAC

    Roles are enforced by middleware. What the frontend hides is cosmetic, and the tests say so.

  • Outbound requests

    One hardened client validates the IP at dial time, re-checks every redirect and strips credentials across hops.

  • Headers

    Strict CSP, HSTS, X-Content-Type-Options, X-Frame-Options and a referrer policy, set for every response.

  • Tokens

    Stored as SHA-256 hashes, single-use, compared in constant time, with a lifetime per purpose.

  • Webhooks

    Signature verified, replay-proof by primary key, and payload data never trusted beyond the object id.

  • GDPR

    Data export and account erasure ship as features, and administrative actions are recorded in an audit log.

232 Go tests run in CI on both database engines, plus 14 Playwright journeys that drive the real production binary: signup and verification, invitations, RBAC refusals, cross-tenant isolation, webhook signatures and duplicate deliveries.

Ship it

One server, three commands, HTTPS included

No build platform to rent, no pipeline to learn. The frontend is embedded in the binary and the migrations run at startup.

  1. 01

    Configure

    make setup asks what you need and writes .env, generating the CSRF key. Forty-one variables are documented; the defaults cover development.

  2. 02

    Build

    make build produces one binary with the Vue app inside. Or hand the same repository to Docker — the production image is a multi-stage build ending on a distroless base.

  3. 03

    Serve

    Traefik handles Let's Encrypt and the HTTP to HTTPS redirect. The app trusts X-Forwarded-For only from the proxy network, and the database is never exposed.

Production, from zero
$ docker network create traefik$ ACME_EMAIL=you@example.com \    docker compose -f deploy/traefik/docker-compose.yml up -d$ cp .env.example .env && make setup$ docker compose -f docker-compose.prod.yml up -d --build

Updating is git pull and the same up -d --build. Back up two volumes: the database and the uploads — blobs live outside the dump.

Development is shorter still: make dev runs on SQLite with no Docker at all, creating and migrating the database file for you.

Stop rebuilding the same foundation.

Accounts, teams, billing, emails, admin, security: the weeks that pass before you write the first line of your actual product. They are already written, tested and documented.

Buy the license — €199