Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copy this file to .env and fill in the values.
# .env is gitignored — never commit real secrets.

FORM_SHARED_SECRET=
99 changes: 25 additions & 74 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,88 +3,39 @@ __pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
dist/
build/

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version
# Virtual environments
.venv/
venv/
ENV/

# celery beat schedule file
celerybeat-schedule
# IDE
.idea/
.vscode/
*.swp
*.swo

# dotenv
.env

# virtualenv
.venv/
venv/
ENV/
# pytest
.pytest_cache/
.coverage
htmlcov/

# mypy
.mypy_cache/

# ruff
.ruff_cache/

# Spyder project settings
.spyderproject
# AWS SAM
.aws-sam/

# Rope project settings
.ropeproject
# OS files
.DS_Store
29 changes: 29 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-include .env
export

DEV_OVERRIDES = Stage=dev FromEmail=hello@tryolabs.com DestinationEmail=german@tryolabs.com FormSharedSecret=$(FORM_SHARED_SECRET)
PROD_OVERRIDES = Stage=prod FromEmail=hello@tryolabs.com DestinationEmail=hello@tryolabs.com FormSharedSecret=$(FORM_SHARED_SECRET)

.PHONY: build deploy-dev deploy-prod logs-dev logs-prod test lint

build:
sam build --use-container

deploy-dev: build
sam deploy --config-env dev --parameter-overrides "$(DEV_OVERRIDES)"

deploy-prod: build
sam deploy --config-env prod --parameter-overrides "$(PROD_OVERRIDES)"

logs-dev:
sam logs --stack-name lambda-mailer-v2-dev --region us-west-2 --tail

logs-prod:
sam logs --stack-name lambda-mailer-v2-prod --region us-west-2 --tail

test:
uv run pytest

lint:
uv run ruff check .
uv run ruff format --check .
116 changes: 90 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,116 @@
# Tryolabs Lambda mailer
# Tryolabs Lambda Mailer

Uses [Zappa](https://github.com/Miserlou/Zappa) to deploy a serverless endpoint that receives a JSON and sends an email to a destination address. Made to support contact forms in our static website.
Serverless contact form endpoint deployed to AWS Lambda via [AWS SAM](https://aws.amazon.com/serverless/sam/). Receives a JSON payload and sends an email using Amazon SES.

Built with [FastAPI](https://fastapi.tiangolo.com/) + [Mangum](https://mangum.fastapiexpert.com/) (ASGI→Lambda adapter).

## Prerequisites

- [uv](https://docs.astral.sh/uv/) (Python package manager)
- [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)
- [Docker](https://www.docker.com/) (required for `sam build --use-container`)
- Valid AWS credentials configured (`~/.aws/credentials` or environment variables)
- A verified SES sender email address in your AWS account

## Setup

_Before you begin, make sure you have a valid AWS account and your [AWS credentials file](https://blogs.aws.amazon.com/security/post/Tx3D6U6WSFGOK2H/A-New-and-Standardized-Way-to-Manage-Credentials-in-the-AWS-SDKs) is properly installed._
**1. Install dependencies:**

```bash
uv sync --all-extras
```

**2. Configure secrets:**

```bash
cp .env.example .env
# Edit .env and fill in FORM_SHARED_SECRET with a strong random value:
openssl rand -hex 32
```

`.env` is gitignored — never commit it.

## Deployment

All deploy commands are in the `Makefile` and read secrets from `.env` automatically.

Deploy to **dev**:

```bash
make deploy-dev
```

Make sure you are in a virtualenv, and run:
Deploy to **production**:

```bash
make deploy-prod
```
$ pip install -r requirements.txt
$ zappa deploy dev

### Environment variables / parameters

| Parameter | Source | Description |
|---|---|---|
| `FromEmail` | `samconfig.toml` | Verified SES sender address |
| `DestinationEmail` | `samconfig.toml` | Address to receive form submissions |
| `FormSharedSecret` | `.env` | Shared secret — Next.js sends this in `X-Form-Secret` header; requests without it are rejected with 403 |

Other deploy config (stack name, region) is in `samconfig.toml`.

## Development

Run the API locally (requires SAM CLI):

```bash
sam local start-api --env-vars env.json
```

To redeploy:
Or directly with uvicorn:

```bash
uv run uvicorn api:app --reload
```

$ zappa update dev
### Linting & formatting

To deploy the production version:
```bash
make lint
# or individually:
uv run ruff check .
uv run ruff format .
```

$ zappa deploy prod
### Type checking

```bash
uv run mypy api.py handler.py
```

### Environment variables
In the `zappa_settings.json` file, you can edit the environment variables for `dev` and `prod`:
### Testing

```javascript
{
"dev": {
...
"environment_variables": {
"FROM_EMAIL": "alan@tryolabs.com",
"DESTINATION_EMAIL": "alan@tryolabs.com"
}
},
...
}
```bash
make test
# or:
uv run pytest
```

## Testing
### Logs

With [httpie](https://httpie.org/), invoke the endpoint0 with the `message.json` test file as payload:
```bash
make logs-dev # tail dev Lambda logs
make logs-prod # tail prod Lambda logs
```

$ http <endpoint> < message.json
## Manual testing

With [httpie](https://httpie.org/), invoke the endpoint with the `message.json` test file:

```bash
http POST <endpoint-url> X-Form-Secret:<secret> < message.json
```

## License

Copyright (c) 2016 [Tryolabs](https://tryolabs.com).

Released under the MIT License (See LICENSE).

Loading