Skip to content

Commit 0d5d2b8

Browse files
Corrected the sender email addresses to correctly use env vars
1 parent bad1650 commit 0d5d2b8

File tree

9 files changed

+203
-12
lines changed

9 files changed

+203
-12
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ DB_NAME=
1313

1414
# Resend
1515
RESEND_API_KEY=
16+
EMAIL_FROM=

.github/workflows/publish.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ jobs:
7070
echo "SECRET_KEY=$(openssl rand -base64 32)" >> _environment
7171
echo "BASE_URL=http://localhost:8000" >> _environment
7272
echo "RESEND_API_KEY=resend_api_key" >> _environment
73+
echo "[email protected]" >> _environment
7374
7475
- name: Setup Graphviz
7576
uses: ts-graphviz/setup-graphviz@v2

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,13 @@ it into the .env file.
145145

146146
Set your desired database name, username, and password in the .env file.
147147

148-
To use password recovery, register a [Resend](https://resend.com/)
149-
account, verify a domain, get an API key, and paste the API key into the
150-
.env file.
148+
To use password recovery and other email features, register a
149+
[Resend](https://resend.com/) account, verify a domain, get an API key,
150+
and paste the API key and the email address you want to send emails from
151+
into the .env file. Note that you will need to [verify a domain through
152+
the Resend
153+
dashboard](https://resend.com/docs/dashboard/domains/introduction) to
154+
send emails from that domain.
151155

152156
### Start development database
153157

docs/installation.qmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ Generate a 256 bit secret key with `openssl rand -base64 32` and paste it into t
132132

133133
Set your desired database name, username, and password in the .env file.
134134

135-
To use password recovery, register a [Resend](https://resend.com/) account, verify a domain, get an API key, and paste the API key into the .env file.
135+
To use password recovery, register a [Resend](https://resend.com/) account, verify a domain, get an API key, and paste the API key and sender email address into the .env file.
136136

137137
If using the dev container configuration, you will need to set the `DB_HOST` environment variable to "host.docker.internal" in the .env file. Otherwise, set `DB_HOST` to "localhost" for local development. (In production, `DB_HOST` will be set to the hostname of the database server.)
138138

docs/static/documentation.txt

Lines changed: 190 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# FastAPI, Jinja2, PostgreSQL Webapp Template
22

3-
![Screenshot of homepage](docs/static/Screenshot.png)
3+
![Screenshot of homepage](docs/static/screenshot.jpg)
44

55
## Quickstart
66

@@ -114,7 +114,7 @@ Generate a 256 bit secret key with `openssl rand -base64 32` and paste it into t
114114

115115
Set your desired database name, username, and password in the .env file.
116116

117-
To use password recovery, register a [Resend](https://resend.com/) account, verify a domain, get an API key, and paste the API key into the .env file.
117+
To use password recovery and other email features, register a [Resend](https://resend.com/) account, verify a domain, get an API key, and paste the API key and the email address you want to send emails from into the .env file. Note that you will need to [verify a domain through the Resend dashboard](https://resend.com/docs/dashboard/domains/introduction) to send emails from that domain.
118118

119119
### Start development database
120120

@@ -542,7 +542,7 @@ Generate a 256 bit secret key with `openssl rand -base64 32` and paste it into t
542542

543543
Set your desired database name, username, and password in the .env file.
544544

545-
To use password recovery, register a [Resend](https://resend.com/) account, verify a domain, get an API key, and paste the API key into the .env file.
545+
To use password recovery, register a [Resend](https://resend.com/) account, verify a domain, get an API key, and paste the API key and sender email address into the .env file.
546546

547547
If using the dev container configuration, you will need to set the `DB_HOST` environment variable to "host.docker.internal" in the .env file. Otherwise, set `DB_HOST` to "localhost" for local development. (In production, `DB_HOST` will be set to the hostname of the database server.)
548548

@@ -988,7 +988,193 @@ Server-side validation remains essential as a security measure against malicious
988988

989989
# Deployment
990990

991-
## Under construction
991+
This application requires two services to be deployed and connected to each other:
992+
993+
1. A PostgreSQL database (the storage layer)
994+
2. A FastAPI app (the application layer)
995+
996+
There are *many* hosting options available for each of these services; this guide will cover only a few of them.
997+
998+
## Deploying and Configuring the PostgreSQL Database
999+
1000+
### On Digital Ocean
1001+
1002+
#### Getting Started
1003+
1004+
- Create a [DigitalOcean](mdc:https:/www.digitalocean.com) account
1005+
- Install the [`doctl` CLI tool](mdc:https:/docs.digitalocean.com/reference/doctl) and authenticate with `doctl auth init`
1006+
- Install the [`psql` client](mdc:https:/www.postgresql.org/download)
1007+
1008+
#### Create a Project
1009+
1010+
Create a new project to organize your resources:
1011+
1012+
```bash
1013+
# List existing projects
1014+
doctl projects list
1015+
1016+
# Create a new project
1017+
doctl projects create --name "YOUR-PROJECT-NAME" --purpose "YOUR-PROJECT-PURPOSE" --environment "Production"
1018+
```
1019+
1020+
#### Set Up a Managed PostgreSQL Database
1021+
1022+
Create a managed, serverless PostgreSQL database instance:
1023+
1024+
```bash
1025+
doctl databases create your-db-name --engine pg --version 17 --size db-s-1vcpu-1gb --num-nodes 1 --wait
1026+
```
1027+
1028+
Get the database ID from the output of the create command and use it to retrieve the database connection details:
1029+
1030+
```bash
1031+
# Get the database connection details
1032+
doctl databases connection "your-database-id" --format Host,Port,User,Password,Database
1033+
```
1034+
1035+
Store these details securely in a `.env.production` file (you will need to set them later in application deployment as production secrets):
1036+
1037+
```bash
1038+
# Database connection parameters
1039+
DB_HOST=your-host
1040+
DB_PORT=your-port
1041+
DB_USER=your-user
1042+
DB_PASS=your-password
1043+
DB_NAME=your-database
1044+
```
1045+
1046+
You may also want to save your database id, although you can always find it again later by listing your databases with `doctl databases list`.
1047+
1048+
#### Setting Up a Firewall Rule (after Deploying Your Application Layer)
1049+
1050+
Note that by default your database is publicly accessible from the Internet, so you should create a firewall rule to restrict access to only your application's IP address once you have deployed the application. The command to do this is:
1051+
1052+
```bash
1053+
doctl databases firewalls append <database-cluster-id> --rule <type>:<value>
1054+
```
1055+
1056+
where `<type>` is `ip_addr` and `<value>` is the IP address of the application server. See the [DigitalOcean documentation](https://docs.digitalocean.com/reference/doctl/reference/databases/firewalls/append/) for more details.
1057+
1058+
**Note:** You can only complete this step after you have deployed your application layer and obtained a static IP address for the application server.
1059+
1060+
## Deploying and Configuring the FastAPI App
1061+
1062+
### On Modal.com
1063+
1064+
The big advantages of deploying on Modal.com are:
1065+
1. that they offer $30/month of free credits for each user, plus generous additional free credit allotments for startups and researchers, and
1066+
2. that it's a very user-friendly platform.
1067+
1068+
The disadvantages are:
1069+
1. that Modal is a Python-only platform and cannot run the database layer, so you'll have to deploy that somewhere else,
1070+
2. that you'll need to make some modest changes to the codebase to get it to work on Modal, and
1071+
3. that Modal offers a [static IP address for the application server](https://modal.com/docs/guide/proxy-ips) only if you pay for a higher-tier plan starting at $250/year, which makes securing the database layer with a firewall rule cost prohibitive.
1072+
1073+
#### Getting Started
1074+
1075+
- [Sign up for a Modal.com account](https://modal.com/signup)
1076+
- Install modal in the project directory with `uv add modal`
1077+
- Run `uv run modal setup` to authenticate with Modal
1078+
1079+
#### Defining the Modal Image and App
1080+
1081+
Create a new Python file in the root of your project, for example, `deploy.py`. This file will define the Modal Image and the ASGI app deployment.
1082+
1083+
1. **Define the Modal Image in `deploy.py`:**
1084+
- Use `modal.Image` to define the container environment. Chain methods to install dependencies and add code/files.
1085+
- Start with a Debian base image matching your Python version (e.g., 3.13).
1086+
- Install necessary system packages (`libpq-dev` for `psycopg2`, `libwebp-dev` for Pillow WebP support).
1087+
- Install Python dependencies using `run_commands` with `uv`.
1088+
- Add your local Python modules (`routers`, `utils`, `exceptions`) using `add_local_python_source`.
1089+
- Add the `static` and `templates` directories using `add_local_dir`. The default behaviour (copying on container startup) is usually fine for development, but consider `copy=True` for production stability if these files are large or rarely change.
1090+
1091+
```python
1092+
# deploy.py
1093+
import modal
1094+
import os
1095+
1096+
# Define the base image
1097+
image = (
1098+
modal.Image.debian_slim(python_version="3.13")
1099+
.apt_install("libpq-dev", "libwebp-dev")
1100+
.pip_install_from_pyproject("pyproject.toml")
1101+
.add_local_python_source("main")
1102+
.add_local_python_source("routers")
1103+
.add_local_python_source("utils")
1104+
.add_local_python_source("exceptions")
1105+
.add_local_dir("static", remote_path="/root/static")
1106+
.add_local_dir("templates", remote_path="/root/templates")
1107+
)
1108+
1109+
# Define the Modal App
1110+
app = modal.App(
1111+
name="your-app-name",
1112+
image=image,
1113+
secrets=[modal.Secret.from_name("your-app-name-secret")]
1114+
)
1115+
```
1116+
1117+
2. **Define the ASGI App Function in `deploy.py`:**
1118+
- Create a function decorated with `@app.function()` and `@modal.asgi_app()`.
1119+
- Inside this function, import your FastAPI application instance from `main.py`.
1120+
- Return the FastAPI app instance.
1121+
- Use `@modal.concurrent()` to allow the container to handle multiple requests concurrently.
1122+
1123+
```python
1124+
# deploy.py (continued)
1125+
1126+
# Define the ASGI app function
1127+
@app.function(
1128+
allow_concurrent_inputs=100 # Adjust concurrency as needed
1129+
)
1130+
@modal.asgi_app()
1131+
def fastapi_app():
1132+
# Important: Import the app *inside* the function
1133+
# This ensures it runs within the Modal container environment
1134+
# and has access to the installed packages and secrets.
1135+
# It also ensures the lifespan function (db setup) runs correctly
1136+
# with the environment variables provided by the Modal Secret.
1137+
from main import app as web_app
1138+
1139+
return web_app
1140+
```
1141+
1142+
For more information on Modal FastAPI images and applications, see [this guide](https://modal.com/docs/guide/webhooks#how-do-web-endpoints-run-in-the-cloud).
1143+
1144+
#### Deploying the App
1145+
1146+
From your terminal, in the root directory of your project, run:
1147+
1148+
```bash
1149+
modal deploy deploy.py
1150+
```
1151+
1152+
Modal will build the image (if it hasn't been built before or if dependencies changed) and deploy the ASGI app. It will output a public URL (e.g., `https://your-username--your-app-name.modal.run`).
1153+
1154+
#### Setting Up Modal Secrets
1155+
1156+
The application relies on environment variables stored in `.env` (like `SECRET_KEY`, `DB_USER`, `DB_PASSWORD`, `DB_HOST`, `DB_PORT`, `DB_NAME`, `RESEND_API_KEY`, `BASE_URL`). These sensitive values should be stored securely using Modal Secrets.
1157+
1158+
Create a Modal Secret either through the Modal UI or CLI. Note that the name of the secret has to match the secret name you used in the `deploy.py` file, above (e.g., `your-app-name-secret`).
1159+
1160+
```bash
1161+
# Example using CLI
1162+
modal secret create your-app-name-secret \
1163+
SECRET_KEY='your_actual_secret_key' \
1164+
DB_USER='your_db_user' \
1165+
DB_PASSWORD='your_db_password' \
1166+
DB_HOST='your_external_db_host' \
1167+
DB_PORT='your_db_port' \
1168+
DB_NAME='your_db_name' \
1169+
RESEND_API_KEY='your_resend_api_key' \
1170+
BASE_URL='https://your-username--your-app-name-serve.modal.run'
1171+
```
1172+
1173+
**Important:** Ensure `DB_HOST` points to your *cloud* database host address, not `localhost` or `host.docker.internal`.
1174+
1175+
#### Testing the Deployment
1176+
1177+
Access the provided Modal URL in your browser. Browse the site and test the registration and password reset features to ensure database and Resend connections work.
9921178

9931179
# Contributing
9941180

docs/static/schema.png

55 KB
Loading

index.qmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ Generate a 256 bit secret key with `openssl rand -base64 32` and paste it into t
116116

117117
Set your desired database name, username, and password in the .env file.
118118

119-
To use password recovery and other email features, register a [Resend](https://resend.com/) account, verify a domain, get an API key, and paste the API key into the .env file. Note that you will need to [verify your domain with Resend](https://resend.com/docs/dashboard/domains/introduction) to send emails, and you will not be able to send emails from the localhost development server.
119+
To use password recovery and other email features, register a [Resend](https://resend.com/) account, verify a domain, get an API key, and paste the API key and the email address you want to send emails from into the .env file. Note that you will need to [verify a domain through the Resend dashboard](https://resend.com/docs/dashboard/domains/introduction) to send emails from that domain.
120120

121121
### Start development database
122122

routers/account.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from fastapi import APIRouter, Depends, BackgroundTasks, Form, Request, Query
66
from fastapi.responses import RedirectResponse
77
from fastapi.templating import Jinja2Templates
8-
from fastapi.exceptions import HTTPException
98
from starlette.datastructures import URLPath
109
from pydantic import EmailStr
1110
from sqlmodel import Session, select

utils/auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ def send_reset_email(email: str, session: Session) -> None:
205205
html_content: str = template.render({"reset_url": reset_url})
206206

207207
params: resend.Emails.SendParams = {
208-
"from": "[email protected]",
208+
"from": os.getenv("EMAIL_FROM", ""),
209209
"to": [email],
210210
"subject": "Password Reset Request",
211211
"html": html_content,
@@ -267,7 +267,7 @@ def send_email_update_confirmation(
267267
})
268268

269269
params: resend.Emails.SendParams = {
270-
"from": "[email protected]",
270+
"from": os.getenv("EMAIL_FROM", ""),
271271
"to": [current_email],
272272
"subject": "Confirm Email Update",
273273
"html": html_content,

0 commit comments

Comments
 (0)