Skip to content

Installation and Quick Start

Copilot edited this page Jul 11, 2026 · 1 revision

Installation and Quick Start

Prerequisites

  • An AWS account, with access to SES, Lambda, API Gateway, DynamoDB, CloudFormation, and IAM.
  • A Cloudflare account managing DNS for the domain(s) you'll send from, plus a Turnstile site (used for spam/bot protection on the public /send endpoint).
  • Python 3.14.
  • AWS SAM CLI.
  • (Optional, only needed for local sam local testing) Java, for the DynamoDB Local jar.

Clone and set up the virtual environment

git clone git@github.com:napalm255/nmailx.git
cd nmailx
make venv   # creates .venv/, installs requirements-dev.txt into it - safe to re-run

make venv only reinstalls dependencies when requirements*.txt actually changes, so repeat runs are fast. If you'd rather manage the virtual environment yourself: python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt.

First deploy

# Generate a JWT signing secret and grab a Turnstile secret key from Cloudflare
export JWT_SIGNING_SECRET=$(openssl rand -hex 32)
export TURNSTILE_SECRET_KEY=<from the Cloudflare Turnstile dashboard>

make deploy-guided   # first time only - sam build, then sam deploy --guided (prompts for stack name/region)

sam deploy --guided writes non-secret settings to samconfig.toml (already committed with sane defaults). Do not let it save the secret parameter values - answer "N" when it asks to save JwtSigningSecret / TurnstileSecretKey to config; every subsequent deploy sources them from your environment instead (see Repeat deploys below).

Bootstrapping your first admin user

# Password is hashed locally, never sent through CloudFormation
make bootstrap-admin EMAIL=you@example.com

# Note the AdminUrl and SendEndpoint from the stack outputs
make outputs

scripts/bootstrap_admin.py is the only way to create the very first admin account - after that, additional users are managed from the dashboard itself (Users -> Add user).

Configuring your first domain

Log into the dashboard at the AdminUrl output, then:

  1. Add a domain (Domains -> Add domain) - the domain your static site sends from, the From/recipient addresses, and rate limits.
  2. Verify it with SES (on the domain's edit page, "Check / request verification") - this returns DKIM CNAME tokens.
  3. Add those DKIM records to Cloudflare DNS (DNS-only, not proxied) - either by hand, or automate it: make cloudflare-dns DOMAIN=mail.example.com ZONE_NAME=example.com CF_API_TOKEN=....
  4. Add a template (Templates -> Add template) - either authored inline, or pointed at a body file in a public GitHub repo.
  5. Copy integration-snippet/nmailx-contact-form.js and example.html into your static site, filling in the endpoint URL, template ID, and your Turnstile site key.

SES sandbox mode

SES accounts start in sandbox mode (can only send to verified addresses, 200/day cap). If your recipient_address is a verified identity, sandbox mode may be enough for a single-owner deployment; for real volume, request production access via an AWS Support case.

Repeat deploys

JWT_SIGNING_SECRET=... TURNSTILE_SECRET_KEY=... make deploy

make deploy runs sam build && sam deploy with those secrets passed as parameter overrides, reading them from your environment - never written to samconfig.toml. Because it's plain CloudFormation under the hood, re-running it is always safe - it computes a changeset and applies only the diff. Editing template.yaml and redeploying can create, update, or delete individual resources depending on what changed - this is the same mechanism that makes Teardown work, just scoped to one resource instead of the whole stack.

Common setup issues

  • sam deploy --guided prompts to save secrets to samconfig.toml - always answer "N". If you accidentally said yes, remove the JwtSigningSecret/TurnstileSecretKey lines from samconfig.toml and rotate both values (redeploy with new ones).
  • SES send fails with a "not authorized" / sandbox error - you're likely still in SES sandbox mode and sending to an unverified address; see the sandbox note above.
  • Turnstile verification fails locally - Turnstile calls a real third-party endpoint; local/dev testing typically stubs it out (see Local Development and Testing) rather than hitting Cloudflare's API directly.
  • DKIM records not validating - make sure they were added as DNS-only (grey cloud) in Cloudflare, not proxied - a proxied CNAME breaks SES's DNS-based domain verification.

Clone this wiki locally