-
Notifications
You must be signed in to change notification settings - Fork 0
Installation and Quick Start
- 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
/sendendpoint). - Python 3.14.
- AWS SAM CLI.
- (Optional, only needed for local
sam localtesting) Java, for the DynamoDB Local jar.
git clone git@github.com:napalm255/nmailx.git
cd nmailx
make venv # creates .venv/, installs requirements-dev.txt into it - safe to re-runmake 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.
# 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).
# 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 outputsscripts/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).
Log into the dashboard at the AdminUrl output, then:
-
Add a domain (Domains -> Add domain) - the domain your static site
sends from, the
From/recipient addresses, and rate limits. - Verify it with SES (on the domain's edit page, "Check / request verification") - this returns DKIM CNAME tokens.
-
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=.... - Add a template (Templates -> Add template) - either authored inline, or pointed at a body file in a public GitHub repo.
-
Copy
integration-snippet/nmailx-contact-form.jsandexample.htmlinto your static site, filling in the endpoint URL, template ID, and your Turnstile site key.
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.
JWT_SIGNING_SECRET=... TURNSTILE_SECRET_KEY=... make deploymake 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.
-
sam deploy --guidedprompts to save secrets tosamconfig.toml- always answer "N". If you accidentally said yes, remove theJwtSigningSecret/TurnstileSecretKeylines fromsamconfig.tomland 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.