Skip to content

Repository files navigation

Receipt Relay

Receipt Relay is the companion template for the DevOps Daily guide to building transactional email with SMTPFast. It sends one receipt through a small FastAPI backend and traces the message from API acceptance to delivery events and verified webhooks.

The project is deliberately small enough to follow in one sitting while still showing the boundaries that matter in a real integration:

  • The SMTPFast API key stays on the server.
  • Pydantic validates receipt data before a quota-consuming send.
  • User-controlled values are escaped in HTML, with a plain-text alternative.
  • The browser receives a provider email ID and follows its delivery trace.
  • Webhook signatures are verified against the raw request body.
  • Tests use an in-memory HTTP transport and never send real email.

Caution

The form sends a real email when valid SMTPFast credentials are configured. Use an inbox you control while working through the guide.

Use this template

Create a repository from this template in GitHub, or clone it directly. Then work through the setup below from the project root.

Prerequisites

  • Python 3.11 or later
  • An SMTPFast account
  • A sending domain verified in SMTPFast
  • A named SMTPFast API key
  • An inbox you control for the live test

Docker is optional.

Quick start

Create a virtual environment and install the application with its development dependencies:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"

Create your local configuration:

cp .env.example .env
chmod 600 .env

Set these two required values in .env:

SMTPFAST_API_KEY=replace-with-your-smtpfast-api-key
SMTPFAST_FROM_EMAIL=receipts@your-verified-domain.com

Start the application:

make dev

Open http://localhost:8080, load the example, enter an inbox you control, and send one receipt.

Configure SMTPFast

Before running a live send:

  1. Add a sending domain in SMTPFast.
  2. Use Connect to Cloudflare if the domain uses Cloudflare DNS, or publish the displayed DNS records manually.
  3. Wait until SMTPFast marks the domain as verified.
  4. Create a named API key and copy it into .env.
  5. Use a SMTPFAST_FROM_EMAIL address on the verified domain.

The API key belongs only in server-side configuration. Do not put it in browser JavaScript, screenshots, commits, or container images.

The current API details are documented by SMTPFast:

How the demo works

  1. The browser submits receipt fields to POST /api/receipts.
  2. FastAPI validates the values and renders HTML and plain-text bodies.
  3. The backend calls POST /emails with the server-side SMTPFast key.
  4. SMTPFast returns an email ID. This means the request was accepted, not that the recipient server accepted the message.
  5. The browser polls GET /api/emails/{email_id} for delivery events.
  6. When configured, signed SMTPFast webhooks are merged into the same trace.

The UI keeps delivery state separate from engagement. An opened event is shown as Open signal because image proxies and security scanners can request the tracking pixel without a person reading the email.

API endpoints

Endpoint Purpose
GET / Receipt Relay interface
GET /health Non-sending health check
GET /api/config Safe public configuration flags
POST /api/receipts Validate and submit one receipt
GET /api/emails/{email_id} Retrieve the delivery trace
POST /webhooks/smtpfast Receive signed SMTPFast events
GET /api/docs FastAPI OpenAPI interface

You can submit a receipt without the browser:

curl --request POST http://localhost:8080/api/receipts \
  --header 'Content-Type: application/json' \
  --data '{
    "customer_name": "Ana Petrova",
    "recipient": "you@example.com",
    "order_id": "ORD-2048",
    "product_name": "Production readiness review",
    "amount_cents": 14900,
    "currency": "EUR"
  }'

The response contains the SMTPFast email ID:

{
  "email_id": "email_abc123",
  "status": "queued",
  "latency_ms": 48
}

Configure webhooks

SMTPFast needs a public HTTPS URL. Register this standard-format endpoint after deploying the application or exposing it through a trusted development tunnel:

https://your-app.example/webhooks/smtpfast

Subscribe only to events the application uses, for example:

[
  "email.sent",
  "email.delivered",
  "email.delivery_delayed",
  "email.bounced",
  "email.failed",
  "email.suppressed",
  "email.opened",
  "email.clicked"
]

Store the returned signing secret as SMTPFAST_WEBHOOK_SECRET. The endpoint rejects missing or invalid signatures and never exposes the secret through /api/config.

The in-memory event store is intentionally tutorial-sized. A production consumer should persist and deduplicate each verified event before returning a successful response.

Run the checks

make check

This runs Ruff linting, Ruff formatting validation, and the complete pytest suite. Tests mock SMTPFast, so they need no credentials and consume no quota.

Run with Docker

docker build -t smtpfast-receipt-relay .
docker run --rm \
  --publish 8080:8080 \
  --env-file .env \
  smtpfast-receipt-relay

The image runs as a non-root user and exposes /health for container and load balancer checks.

Configuration

Variable Required Default Purpose
SMTPFAST_API_KEY Yes None Server-side key for sends and trace reads
SMTPFAST_FROM_EMAIL Yes None Sender on a verified SMTPFast domain
SMTPFAST_BASE_URL No https://smtpfa.st/api/v1 SMTPFast API base URL
SMTPFAST_TIMEOUT_SECONDS No 20 Upstream request timeout
SMTPFAST_WEBHOOK_SECRET For webhooks None HMAC signing secret for webhook verification
APP_ACCESS_TOKEN No None Shared code for a short-lived public demo

APP_ACCESS_TOKEN deters casual use of a public demo. It is not user identity, tenant authorization, or production access control.

Production boundaries

This repository demonstrates the integration, not a complete checkout system. Before adapting it to production:

  • Load trusted order data from your database instead of accepting totals from a browser.
  • Add idempotency so retries and double-clicks cannot send duplicate receipts.
  • Store the SMTPFast email ID with the business record that caused the send.
  • Persist webhook events with a unique constraint on provider event ID.
  • Add real user and tenant authorization.
  • Apply per-user, per-tenant, and global quotas.
  • Define retention and deletion behavior for recipient data.
  • Monitor API failures, bounce categories, delivery latency, and webhook retries.

Project layout

.
├── app/
│   ├── config.py        # Environment-backed settings
│   ├── main.py          # FastAPI routes, security headers, and webhook verification
│   ├── models.py        # Receipt, trace, and webhook validation
│   ├── smtpfast.py      # SMTPFast client and receipt renderer
│   ├── trace_store.py   # Bounded tutorial event store
│   └── static/          # Browser interface and delivery timeline
├── tests/               # Mocked API, error, trace, and webhook tests
├── .env.example
├── Dockerfile
├── Makefile
└── pyproject.toml

License

This project is available under the MIT License.

About

Companion FastAPI template for tracing transactional email delivery with SMTPFast.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages