Rewrite the service in Go and ship it as a static container

The PHP app, Apache, and .htaccess are replaced by a single Go binary
(standard library only). Routing, the JSON response, the landing page,
the shared email design, and all four parsers are ported with the same
output; JSON object keys are kept in sender order via an ordered decoder.

- mail.go: SMTP delivery with quoted-printable body and RFC 2047 subject
- main.go: routing, client IP from the proxy, /healthz, -check flag
- render.go: email shell, escaping, table helpers, generic renderer
- parser_*.go: github, grafana, json, wxinteract
- main_test.go: handler, response shape, IP forwarding, every parser
- Dockerfile: multi-stage build to distroless static, non-root, HEALTHCHECK
- deploy/webhooks.dixon.cx.service: systemd unit for the container
- README and PARSER_PROMPT updated for Go; IMPLEMENTATION_SUMMARY removed
This commit is contained in:
Andy Dixon
2026-09-12 19:59:54 +01:00
parent a61da1f6e3
commit 2d0f98e041
26 changed files with 1739 additions and 2301 deletions
+44 -12
View File
@@ -1,6 +1,6 @@
# Webhook Email Forwarder
A simple, elegant PHP solution for debugging webhooks by forwarding them directly to your email.
A single-binary Go service for debugging webhooks by forwarding them directly to your email.
## 🌟 Features
@@ -176,7 +176,7 @@ See also the [Webex Interact Guide](WEBEX_INTERACT_GUIDE.md).
### Creating Your Own Parser
A parser is one file in `parsers/` exposing `{name}Parse($rawBody, $headers, $metadata)` that returns `['html' => ..., 'subject' => ...]` or `false`. Build the body from `.section`, `table.metadata`, and `.data-box` blocks, escape free text with `esc()`, and wrap it with `emailShell()` from `parsers/helpers.php` so it picks up the shared design. See the [Parser Development Guide](PARSER_PROMPT.md) for details.
A parser is one Go function of type `Parser` in `parser_{name}.go`, registered in the `parsers` map in `main.go`. Build the body with `section`, `table`, and `row`, escape free text with `esc`, and wrap it with `emailShell` so it picks up the shared design. See the [Parser Development Guide](PARSER_PROMPT.md) for details.
---
@@ -298,23 +298,41 @@ Hello! Your appointment is confirmed for tomorrow at 2 PM.
## 📦 Installation
1. Clone the repository:
### Run the binary
```bash
git clone https://github.com/andydixon/webhook.git
cd webhook
go build -o webhooks .
LISTEN=127.0.0.1:8080 SMTP_ADDR=127.0.0.1:25 MAIL_FROM=no-reply@example.com ./webhooks
```
2. Upload the files to your web server (ensure PHP is installed and configured)
Put a reverse proxy (nginx, Caddy) in front for TLS. The service reads `X-Real-IP` / `X-Forwarded-For` from a private-address proxy to report the real client IP.
3. Ensure the web server has permission to send emails (PHP `mail()` function)
### Run the container
4. Access the root URL to view the documentation page
```bash
docker build -t webhooks.dixon.cx:latest .
docker run --rm -p 127.0.0.1:29593:8080 webhooks.dixon.cx:latest
```
The image is a static binary on `distroless`, runs as non-root, listens on `:8080`, and relays mail to `172.17.0.1:25` (the Docker host's SMTP server) by default. `deploy/webhooks.dixon.cx.service` is the systemd unit used in production.
### Configuration
| Variable | Default | Meaning |
|----------|---------|---------|
| `LISTEN` | `127.0.0.1:8080` | Address to listen on (`:8080` in the container) |
| `SMTP_ADDR` | `127.0.0.1:25` | SMTP relay, no auth or TLS (`172.17.0.1:25` in the container) |
| `MAIL_FROM` | `no-reply@dixon.cx` | Envelope and From address |
| `PUBLIC_HOST` | `webhooks.dixon.cx` | Host shown in email footers |
`/healthz` returns `ok`, and `webhooks -check` exits non-zero if a running instance is unhealthy (used by the image's `HEALTHCHECK`).
## 🔧 Requirements
- PHP 8.0 or higher
- Web server (Apache, Nginx, etc.)
- PHP `mail()` function enabled
- Outbound SMTP/email capability
- Go 1.24 or newer to build (no third-party dependencies), or Docker
- An SMTP server that accepts mail from the service's address
## 📧 What You Receive
@@ -331,7 +349,7 @@ The standard (no parser) email contains:
## 🔒 Security Features
- **Email Validation**: Only valid email addresses are accepted
- **XSS Protection**: All output is escaped with `htmlspecialchars()`; free-text values go through `esc()`, which also preserves line breaks
- **XSS Protection**: All output is HTML-escaped; free-text values go through `esc`, which also preserves line breaks
- **No Data Storage**: Webhook data is forwarded immediately and not stored
- **Error Logging**: Failed email attempts are logged for monitoring
@@ -388,6 +406,12 @@ If the email address in the URL is invalid or missing, you get a `400 Bad Reques
### Unknown Parser or Unparseable Payload
The request still succeeds. The email uses the standard raw format and the response reports the parser name you asked for.
### Mail Delivery Failure
If the SMTP relay rejects the message you get a `502` with `"error": "mail_failed"`, so the sending service will retry rather than believe the webhook was delivered.
### Body Too Large
Bodies over 10 MB get a `413` with `"error": "body_too_large"`.
## 📄 License
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
@@ -408,6 +432,14 @@ Give a ⭐️ if this project helped you!
## 📜 Version History
- **3.0.0** - Go Rewrite
- ♻️ The whole service is now a single Go binary with no dependencies; PHP, Apache, and `.htaccess` are gone
- 🐳 Static `distroless` image, non-root, built-in health check
- 📧 Mail goes over SMTP with a quoted-printable body and an RFC 2047 subject, so emoji subjects survive every relay
- 🔁 A failed send returns `502 mail_failed` instead of a false `200`
- 🧪 `go test ./...` covers routing, the JSON response, IP forwarding, and every parser
- Email output and the JSON response are byte-for-byte compatible with 2.1.0, with JSON object keys kept in sender order
- **2.1.0** - JSON Response and Email Redesign
- ✨ **NEW**: Structured JSON response for every request, including errors
- ✨ **NEW**: Grafana / Prometheus Alertmanager parser (`/grafana`)
@@ -432,4 +464,4 @@ Give a ⭐️ if this project helped you!
---
Built with PHP and ADHD medication 💊
Built with Go and ADHD medication 💊