Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ name: release
# 3. Publishes a GitHub release with .tar.gz archives + checksums.
# 4. Pushes images to docker.io/mailtrap/mailtrap-local + ghcr.io/mailtrap/mailtrap-local.
# 5. Opens a PR in mailtrap/homebrew-local updating Formula/mailtrap-local.rb.
# 6. Opens a PR in mailtrap/mailtrap-nur updating pkgs/mailtrap-local/default.nix.
# 7. Verifies Homebrew + NUR PRs exist (goreleaser soft-fails those errors).

on:
push:
Expand All @@ -19,6 +21,7 @@ permissions:

jobs:
release:
environment: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand Down Expand Up @@ -54,12 +57,43 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

# nix-hash required by goreleaser's `nix:` publisher (NUR PR).
- uses: cachix/install-nix-action@v31

- name: Run goreleaser
uses: goreleaser/goreleaser-action@v7
with:
version: latest
args: release --clean
env:
# Default token covers this repo. Tap updates need HOMEBREW_TAP_TOKEN.
# Default token covers this repo. Cross-repo publishes (homebrew-local, mailtrap-nur, …)
# need GORELEASER_TOKEN with write to those repos.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
GORELEASER_TOKEN: ${{ secrets.GORELEASER_TOKEN }}

# Goreleaser package-manager pipes (brew, nix, …) ContinueOnError=true, so
# PR open failures only log. Fail loud if expected cross-repo PRs missing.
# Head branch defaults to {{ .ProjectName }}-{{ .Version }} when
# pull_request.enabled is true.
- name: Verify Homebrew and NUR package PRs opened
env:
GH_TOKEN: ${{ secrets.GORELEASER_TOKEN }}
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
head="mailtrap-local-${version}"
failed=0
for repo in mailtrap/homebrew-local mailtrap/mailtrap-nur; do
url=$(gh pr list -R "$repo" \
--head "$head" \
--state all \
--json url \
--jq '.[0].url // empty')
if [ -z "$url" ]; then
echo "::error::Expected PR with head '${head}' not found in ${repo} (goreleaser may have soft-failed)"
failed=1
else
echo "${repo} PR found: $url"
fi
done
exit "$failed"
34 changes: 30 additions & 4 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
# Channels published:
# 1. GitHub Releases — .tar.gz binary archives (darwin/linux × amd64/arm64)
# 2. Homebrew tap — PR updating Formula/mailtrap-local.rb in mailtrap/homebrew-local
# 3. Docker Hub — mailtrap/mailtrap-local:{version,latest}
# 4. GHCR — ghcr.io/mailtrap/mailtrap-local:{version,latest}
# 3. NUR — PR updating pkgs/mailtrap-local/default.nix in mailtrap/mailtrap-nur
# 4. Docker Hub — mailtrap/mailtrap-local:{version,latest}
# 5. GHCR — ghcr.io/mailtrap/mailtrap-local:{version,latest}
#
# The releaser needs:
# - GITHUB_TOKEN with write to mailtrap/mailtrap-local
# - HOMEBREW_TAP_TOKEN for the homebrew-local formula PR
# - GORELEASER_TOKEN for cross-repo publishes (homebrew-local, mailtrap-nur, …)
# - DOCKERHUB_USERNAME + DOCKERHUB_TOKEN for the docker push
# - ghcr.io auth via GITHUB_TOKEN (free, no extra secret)
#
Expand Down Expand Up @@ -112,6 +113,7 @@ release:
---
**Channels:**
- Homebrew: `brew install mailtrap/local/mailtrap-local`
- Nix: `nix run github:mailtrap/mailtrap-nur#mailtrap-local`
- Docker: `docker run -p 3535:3535 -p 3550:3550 mailtrap/mailtrap-local:{{ .Tag }}`
- Binary: download the platform tarball above and `tar xzf`.

Expand All @@ -130,7 +132,7 @@ brews:
owner: mailtrap
name: homebrew-local
# Cross-repo token; GITHUB_TOKEN cannot write the tap.
token: "{{ .Env.HOMEBREW_TAP_TOKEN }}"
token: "{{ .Env.GORELEASER_TOKEN }}"
pull_request:
enabled: true
directory: Formula
Expand All @@ -153,6 +155,30 @@ brews:
test: |
assert_match version.to_s, shell_output("#{bin}/mailtrap-local --version")

# ---------------------------------------------------------------------
# NUR — on every tagged release, open a PR that updates
# pkgs/mailtrap-local/default.nix in mailtrap/mailtrap-nur (same pattern
# as the Homebrew tap). Needs nix-hash on PATH (see release.yml).
# ---------------------------------------------------------------------
nix:
- name: mailtrap-local
ids: [mailtrap-local]
repository:
owner: mailtrap
name: mailtrap-nur
token: "{{ .Env.GORELEASER_TOKEN }}"
pull_request:
enabled: true
Comment thread
leonid-shevtsov marked this conversation as resolved.
path: pkgs/mailtrap-local/default.nix
homepage: "https://github.com/mailtrap/mailtrap-local"
description: "Local email sandbox + catcher: SMTP + Web UI + JSON API in one binary"
license: mit
main_program: mailtrap-local
# Default install copies the binary; add sendmail-replacement symlink
# (same as Homebrew Formula).
extra_install: |
ln -s mailtrap-local $out/bin/mailtrap-sendmail

# ---------------------------------------------------------------------
# Docker images — built once per arch, then combined into a manifest
# list so `docker pull` picks the right binary automatically.
Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Browse caught messages, preview rendered HTML, and inspect client compatibility
| Storage | SQLite via [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite) (pure Go — no CGo) |
| Web UI | React 19 + tailwindcss + Radix UI + Vite, embedded into the binary at build time via `//go:embed` |
| Realtime | Plain WebSocket (`/cable`) |
| Distribution | `brew install`, `docker run`, single static binary (macOS + Linux; Windows not yet) |
| Distribution | `brew install`, `nix run`, `docker run`, single static binary (macOS + Linux; Windows not yet) |

## Quick start

Expand All @@ -53,6 +53,17 @@ brew services start mailtrap-local

Then open **http://127.0.0.1:3550** and point your app's SMTP client at **127.0.0.1:3535**.

### Nix

```sh
nix run github:mailtrap/mailtrap-nur#mailtrap-local
# or
nix profile add github:mailtrap/mailtrap-nur#mailtrap-local
# Nix older than 2.25: nix profile install …
```

Package lives in [`mailtrap/mailtrap-nur`](https://github.com/mailtrap/mailtrap-nur); goreleaser opens a PR there on every tag (same flow as the Homebrew tap).

### Docker

```sh
Expand Down Expand Up @@ -144,11 +155,11 @@ mailtrap-local/
├── scripts/build.sh # frontend build + go build → bin/mailtrap-local
├── Dockerfile # multi-stage source build (Node → Go → distroless)
├── Dockerfile.goreleaser # release-time wrapper (goreleaser-built binary → distroless)
├── .goreleaser.yaml # release pipeline: binaries + Homebrew tap + Docker Hub + GHCR
├── .goreleaser.yaml # release pipeline: binaries + Homebrew + NUR + Docker Hub + GHCR
└── .github/workflows/ # ci.yml + release.yml
```

The Homebrew formula lives in a separate tap repo (`mailtrap/homebrew-local`); `goreleaser` opens a formula PR there on every tag.
The Homebrew formula lives in `mailtrap/homebrew-local`; the Nix package lives in `mailtrap/mailtrap-nur`. `goreleaser` opens a PR in each on every tag.

## API

Expand Down
Loading