https://github.com/jfroy/etampe
A small SMTP server bridge to Cloudflare Email Sending
https://github.com/jfroy/etampe
cloudflare email smtp
Last synced: about 1 month ago
JSON representation
A small SMTP server bridge to Cloudflare Email Sending
- Host: GitHub
- URL: https://github.com/jfroy/etampe
- Owner: jfroy
- License: apache-2.0
- Created: 2026-05-19T21:40:10.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-06-04T23:43:39.000Z (2 months ago)
- Last Synced: 2026-06-05T01:11:57.468Z (2 months ago)
- Topics: cloudflare, email, smtp
- Language: Go
- Homepage:
- Size: 78.1 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# 💌 Étampe
`etampe` is a small SMTP-to-Cloudflare Email Sending bridge for homelab Kubernetes clusters. It accepts SMTP on port `2525`, parses the message into Cloudflare's REST payload, sends through the Cloudflare Email Sending API, exposes `/healthz`, `/readyz`, and `/metrics` on port `8080`, and can export OpenTelemetry traces and metrics over OTLP.
## Configuration
Prerequisites:
- A domain onboarded to [Cloudflare Email Service](https://developers.cloudflare.com/email-service/get-started/send-emails/) (SPF, DKIM, and bounce MX records configured).
- A Cloudflare API token with the **Account / Email Service: Edit** permission scoped to the target account.
Required:
- `CLOUDFLARE_ACCOUNT_ID`
- `CLOUDFLARE_API_TOKEN`: API token with **Account / Email Service: Edit** permission.
Common options:
- `CLOUDFLARE_FROM`: optional sender override for verified Cloudflare senders.
- `SMTP_USERNAME` and `SMTP_PASSWORD`: enable SMTP AUTH PLAIN when both are set.
- `SMTP_ALLOW_INSECURE_AUTH`: default `true` for private cluster networks; use TLS or NetworkPolicy when SMTP auth is enabled.
- `SMTP_ADDR`: default `:2525`.
- `HTTP_ADDR`: default `:8080`.
- `OTEL_EXPORTER_OTLP_ENDPOINT`: enables OTLP trace and metric export.
Notes:
- Cloudflare `429` and `5xx` responses are returned to SMTP clients as temporary `451` failures; sender queues should retry.
- Prometheus and OTLP metrics are both emitted. Avoid ingesting both into the same backend unless you de-duplicate.
- SMTP certificates are loaded at startup; restart the pod after Kubernetes secret rotation.
- SMTP `RCPT TO` is authoritative. `To:` and `Cc:` recipients not present in the envelope are stripped from the outbound Cloudflare payload, envelope recipients missing from visible headers are sent as `bcc`, and if no visible recipient remains the first envelope recipient is promoted to `to`.
## Run
```sh
docker run --rm -p 2525:2525 -p 8080:8080 \
-e CLOUDFLARE_ACCOUNT_ID=... \
-e CLOUDFLARE_API_TOKEN=... \
ghcr.io/jfroy/etampe:latest
```
## Kubernetes
Create a secret:
```sh
kubectl create secret generic etampe \
--from-literal=CLOUDFLARE_ACCOUNT_ID=... \
--from-literal=CLOUDFLARE_API_TOKEN=...
```
Apply a private `ClusterIP` deployment:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: etampe
spec:
selector:
matchLabels:
app: etampe
template:
metadata:
labels:
app: etampe
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: etampe
image: ghcr.io/jfroy/etampe:latest
envFrom:
- secretRef:
name: etampe
ports:
- name: smtp
containerPort: 2525
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /readyz
port: http
livenessProbe:
httpGet:
path: /healthz
port: http
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
---
apiVersion: v1
kind: Service
metadata:
name: etampe
spec:
selector:
app: etampe
ports:
- name: smtp
port: 2525
targetPort: smtp
- name: http
port: 8080
targetPort: http
```
Point in-cluster apps at `etampe.default.svc.cluster.local:2525`. Scrape `http://etampe:8080/metrics`.
## See also
- https://github.com/imrasalghul/mailbridge