SMTP server to forward messages to shoutrrr endpoints
  • Go 96%
  • Makefile 2.7%
  • Dockerfile 1.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
butterrobot 16a1d6cdaa
All checks were successful
CI / goreleaser-lint (push) Successful in 4s
CI / format (push) Successful in 2m34s
CI / lint (push) Successful in 3m4s
CI / test (push) Successful in 2m54s
CI / build (push) Successful in 2m54s
Release / release (push) Successful in 3m3s
ci: rename the FORGEJO_TOKEN secret to ACTIONS_TOKEN (FMG-6) (#12)
Both `secrets.FORGEJO_TOKEN` references in `.github/workflows/release.yml` are now `secrets.ACTIONS_TOKEN`: the registry login password and the `GITEA_TOKEN` handed to GoReleaser. Those were the only two occurrences anywhere in the repository — nothing else, including the README and the goreleaser config, named the secret.

Nothing else about the release job changes: `-u ${{ github.actor }}` and `GORELEASER_FORCE_TOKEN: gitea` are untouched.

## Needs a secret to exist before the next tag

The rename only redirects which secret the job reads. `ACTIONS_TOKEN` has to be defined on the repository (or its owner/org) with the same reach the release needs — `write:package` on `git.nakama.town/fmartingr/`, plus repo write for the release upload — or the release job will get an empty value and repeat the `401 Unauthorized: reqPackageAccess` failure from FMG-5. Note that the login step will still print `Login Succeeded` in that case, for the reason described on #10.

I could not verify the secret exists: `tea actions secret list` refuses for this credential (`user should be the owner of the repo`), and Forgejo does not expose a secret's value, owner, or scopes through the API.

## Checks

CI-config change only, no Go code touched. I parsed both workflow files to confirm they are still valid YAML and that the release job's env block resolves to `GITEA_TOKEN: ${{ secrets.ACTIONS_TOKEN }}`. There is no Go toolchain, golangci-lint, or goreleaser in this environment, so the repository's `format` / `lint` / `test` / `build` / `check` targets were not run locally — CI covers them on this PR.

Closes FMG-6

Reviewed-on: #12
2026-09-08 20:09:53 +02:00
.github/workflows ci: rename the FORGEJO_TOKEN secret to ACTIONS_TOKEN (FMG-6) (#12) 2026-09-08 20:09:53 +02:00
cmd deps: upgrade Go to 1.27.1, refresh dependencies and drop gotoolkit (#8) (FMG-2) 2026-09-07 21:27:03 +02:00
.gitignore dev: updated golangci-lint to latest and fixed issues 2026-02-04 12:59:15 +01:00
.goreleaser.yml ci: migrate from Woodpecker to Forgejo Actions and simplify dev tooling (#6) 2026-04-07 18:22:04 +02:00
backend.go bugfix: fix SMTP authentication bypass, remote DoS, and silent delivery failures (#9) (FMG-3) 2026-09-07 22:27:04 +02:00
backend_test.go bugfix: fix SMTP authentication bypass, remote DoS, and silent delivery failures (#9) (FMG-3) 2026-09-07 22:27:04 +02:00
config.go bugfix: fix SMTP authentication bypass, remote DoS, and silent delivery failures (#9) (FMG-3) 2026-09-07 22:27:04 +02:00
config_test.go bugfix: fix SMTP authentication bypass, remote DoS, and silent delivery failures (#9) (FMG-3) 2026-09-07 22:27:04 +02:00
Containerfile deps: upgrade Go to 1.27.1, refresh dependencies and drop gotoolkit (#8) (FMG-2) 2026-09-07 21:27:03 +02:00
email.go bugfix: fix SMTP authentication bypass, remote DoS, and silent delivery failures (#9) (FMG-3) 2026-09-07 22:27:04 +02:00
email_test.go bugfix: fix SMTP authentication bypass, remote DoS, and silent delivery failures (#9) (FMG-3) 2026-09-07 22:27:04 +02:00
go.mod deps: upgrade Go to 1.27.1, refresh dependencies and drop gotoolkit (#8) (FMG-2) 2026-09-07 21:27:03 +02:00
go.sum deps: upgrade Go to 1.27.1, refresh dependencies and drop gotoolkit (#8) (FMG-2) 2026-09-07 21:27:03 +02:00
LICENSE Initial commit 2024-11-13 15:32:54 +01:00
Makefile deps: upgrade Go to 1.27.1, refresh dependencies and drop gotoolkit (#8) (FMG-2) 2026-09-07 21:27:03 +02:00
README.md bugfix: fix SMTP authentication bypass, remote DoS, and silent delivery failures (#9) (FMG-3) 2026-09-07 22:27:04 +02:00
server.go deps: upgrade Go to 1.27.1, refresh dependencies and drop gotoolkit (#8) (FMG-2) 2026-09-07 21:27:03 +02:00
server_test.go deps: upgrade Go to 1.27.1, refresh dependencies and drop gotoolkit (#8) (FMG-2) 2026-09-07 21:27:03 +02:00

smtp2Shoutrrr

A simple SMTP server that forwards incoming emails to a Shoutrrr supported service.

Installing

First generate a new configuration file following this example:

# config.toml
# Port the SMTP server will listen on
Port = 11025

# Credentials for the SMTP server (required — the server refuses to start without them)
Username = "user"
Password = "nometokens"

# Configure recipients to forward emails to

# Multiple Targets (Recommended)
# Use the Targets array to send notifications to multiple services
[[Recipients]]
# Email addresses to forward emails from
Addresses = ["user@example.com"]
# Shoutrrr services to forward emails to
# See shoutrrr documentation: https://containrrr.dev/shoutrrr/
Targets = [
    "ntfy://ntfy.sh/my-ntfy-topic?tags=email",
    "discord://token@id",
    "slack://token@channel"
]

# Single Target (Deprecated)
# The Target field is still supported for backward compatibility
[[Recipients]]
Addresses = ["legacy@example.com"]
Target = "ntfy://ntfy.sh/legacy-topic?tags=thing"  # Will show deprecation warning

# Note: Repeat [[Recipients]] as needed

# Optional: Configure a catch-all recipient for unmatched email addresses
[CatchAll]
# Shoutrrr services to forward unmatched emails to
Targets = ["ntfy://ntfy.sh/catch-all-topic?tags=unmatched"]

The server refuses to start if the configuration cannot forward anything — no recipients and no catch-all, a recipient without addresses or usable targets, or a mistyped table name such as [[Recipient]], which TOML would otherwise accept silently. It also refuses to start without a Username and a Password: these used to fall back to username/password, which left the authentication gate open to the most obvious guess there is.

Clients must authenticate with the configured Username and Password before starting a mail transaction; an unauthenticated client is refused rather than relayed. A client that used to send without issuing AUTH has to be configured with the credentials above.

The reply the sending client gets reflects what happened to the notification: 250 once at least one target has accepted it, 451 when none did so the sender retries, and 550 for a message that cannot be parsed at all — a malformed Content-Type, for instance — which no retry could fix. A partial failure is logged but still accepted, since a retry would redeliver to every target and duplicate the notification on the ones that already have it.

From releases

  • Grab the latest release from the releases page
  • Put the configuration file in the same directory as the binary
  • Run the binary for your appropriate platform

From source (development)

  • Clone this repository
  • Put the configuration file in the repository folder
  • Run make quick-run

Using docker

  • Create a config.toml file as described above
  • Run the docker image mounting the Config.toml file as /config.toml and exposing the configured port:
docker run -v /path/to/config.toml:/config.toml \
    -p 11025:11025 \
    git.nakama.town/fmartingr/smtp2shoutrrr:latest

Development

Run the server with:

make quick-run

Send a test email with:

This will read the config.toml in the current directory to set the appropriate SMTP client configuration.

go run ./cmd/sendmail/.