Important
Thunderbird Accounts is still in active development and is not ready for any active use.
There is codebase documentation available at https://pro-services-docs.thunderbird.net/en/latest.
This project uses uv.
Thunderbird Accounts is a Django 5.2 application. Please read up on Django before diving in.
Docker is the recommended way to run the server.
During development it's great to have these handy!
| Service | Exposed Port | Remarks |
|---|---|---|
| postgres | 5433 | Accounts DB |
| kcpostgres | 5434 | Keycloak DB |
| redis | 6380 | Redis |
| redis | 8071 | Redis Insights |
| accounts | 8087 | Accounts Web |
| flower | 5555 | Celery task monitoring |
| vite-dev | 5173 | Used on Account's frontend for hot-reloading. |
| mailpit | 8025 | Mailpit Web |
| mailpit | 1025 | Mailpit SMTP |
| stalwart | 443 | Stalwart Admin |
| stawart | 8080 | Stalwart Admin |
| stalwart | 8081 | Stalwart JMAP |
| stalwart | 143 | Stalwart IMAP |
| stalwart | 993 | Stalwart IMAP TLS |
| stalwart | 25 | Stalwart SMTP |
| stalwart | 465 | Stalwart SMTP TLS |
Make sure you have uv up and running.
Add the following to /etc/hosts:
127.0.0.1 keycloak
127.0.0.1 stalwart
Ensure you have uv setup and the project bootstrapped by running:
uv sync
uv run bootstrap.pyThis will create a virtual environment if needed and sync the latest project dependencies to your local environment.
The command is safe to run again after future schema changes.
The project comes with some optional dependencies such as cli tools, tools for building the docs, and tools for working with our Subscription app powered by Paddle.
These can be installed be appending --extra <optional dependency> like so:
uv sync --extra cli --extra docs --extra subscriptionIf you find your environment in a troubled state (it happens) you can add the option --from-scratch to bootstrap like
so:
uv run bootstrap.py --from-scratchThis will overwrite your local .env with the contents of .env.example, copy over a fresh copy of the stalwart config
from config.toml.example to mail/etc/config.toml and delete your stalwart's internal db mail/data.
Additionally, you'll want to remove the volumes for both postgres and kcpostgres to clear accounts and keycloak's database.
Accounts integrates mozilla-django-oidc. Included in this repo is a
basic development config for keycloak, and a keycloak environment defined in docker-compose.yml. There's nothing
stopping you from using a different OIDC provider. Please refer to the package documentation and your local .env file
for settings you may need to change.
For local Docker usage, keep the default keycloak hostname in .env. If your browser redirects to
http://keycloak:8999, that is expected; check the /etc/hosts entry above if the page does not load.
Once you have the project bootstrapped you'll want to actually run the project via docker:
docker compose up --build -V(Note: If you're not attached to the docker group you may need to add sudo before the above command.)
Most source files are mounted into the development containers:
- Changes under
assets/should hot-reload through thevite-devservice. - Changes under
src/thunderbird_accounts/should restart theaccountsservice automatically through Uvicorn reload. - Changes to Django templates should also be picked up by the reload process.
If a change does not appear to take effect, restart only the affected service:
docker compose restart accounts
docker compose restart vite-devRebuild only when changing image or dependency inputs such as Dockerfile, pyproject.toml, uv.lock, package.json, or package-lock.json:
docker compose up --buildThe first boot may take a while as:
- Keycloak imports realm / user information from
keycloak/data/import - Accounts pulls the latest Paddle product and subscription information (if you have the Paddle setup.)
Please wait until the containers are fully booted before continuing.
A variety of basic development admin accounts are provided to help folks boot the project and start working.
You can access the login / dashboard at http://localhost:8087/. If you are not logged in you will be taken to a keycloak login screen. You can use the following credentials to proceed:
Username: admin@example.org
Password: admin
From here you can create an email account.
The default admin user is also setup you use Django's admin panel available at http://localhost:8087/admin/.
You may also need create a subscription plan, which can be done at http://localhost:8087/admin/subscription/plan/add/.
Stalwart is located at http://localhost:8080/. Currently, there is a bug preventing instances connected to an external OIDC directory server from logging into the admin panel.
If you need to access the admin panel you can modify your mail/etc/config.toml and edit [storage].directory from
kc to internal, and restart the docker container.
From there you can login with the following credentials:
Username: admin
Password: accounts
You generally won't need to connect to Stalwart's admin panel unless you need to verify account status or debug api
calls. Make sure to change the [storage].directory key back to kc and restart the docker container when you're
finished.
Keycloak is the auth/OIDC provider that is setup by default. Two realms are imported the default master realm and the
tbpro realm.
If you like to login to Keycloak's admin interface you can access that at http://keycloak:8999/admin/master/console/ with the following credentials:
Username: admin
Password: admin
From there you can switch to the tbpro realm and access settings that will affect Account's and Stalwart's login.
Additionally, you can access a simple user management portal under the tbpro realm available at http://keycloak:8999/realms/tbpro/account. Since this within the tbpro realm you can login to any account you created for accounts including admin@example.org.
Legal documents (Terms of Service, Privacy Policy) are stored as markdown source files under
assets/legal/ and served to the frontend as pre-rendered HTML within src/thunderbird_accounts/legal/templates/.
assets/legal/
├── tos/
│ └── v1.0/
│ ├── en.md # Markdown source (source of truth)
└── privacy/
└── v1.0/
├── en.md
src/thunderbird_accounts/legal/templates/
├── tos/
│ └── v1.0/
│ └── en.html # Pre-rendered HTML (served by the API)
└── privacy/
└── v1.0/
└── en.html
Each version directory can contain multiple locale files (e.g. de.md, fr.md). The API falls
back to en.html when a requested locale is not available.
- Create a new versioned directory, e.g.
assets/legal/tos/v2.0/ - Write the markdown source file, e.g.
en.md - Run the conversion command to generate the HTML:
uv run python manage.py convert_legal_docs
- Commit both the
.mdand.htmlfiles - In Django admin, create or update the
LegalDocumentrecord -- setcontent_path(e.g.tos/v2.0),version, and checkis_current. Saving will automatically unsetis_currenton the previous version of the same document type. - If adding new locales, update the SUPPORTED_LEGAL_LANGUAGES value in settings.py
If you edit an existing markdown file, re-run the conversion command to update the HTML:
uv run python manage.py convert_legal_docsThis converts all *.md files under assets/legal/ and writes a sibling .html for each.
Apps are feature of django we can use to create re-usable modules with. We mostly just use them to separate out and organize components. Apps can depend on and/or require other internal apps, there's no hard rule here.
Ensure to nest all internal apps inside src/thunderbird_accounts by appending the destination path after the command:
mkdir -p src/thunderbird_accounts/<app name> && ./manage.py startapp <app name> src/thunderbird_accounts/<app name>
Once the app is created go to src/thunderbird_accounts/<app name>/apps.py and prepend thunderbird_accounts. to
AuthConfig.name so it looks like thunderbird_accounts.<app name>.
Ensure you have the requirements in docs installed and run the following command in the project's root folder:
sphinx-build docs buildFeature flags are stored in localStorage and read at runtime to toggle UI behavior.
| Key | Values | Description |
|---|---|---|
feature.show-connect-now |
true |
Shows the "Connect Now" action card on the desktop panel, which launches Thunderbird Desktop via a custom protocol URL. |
Make sure that the containers are already running.
To run all tests:
./run-tests.sh thunderbird_accountsthis will execute the following command for you:
docker compose exec accounts uv run python manage.py test thunderbird_accountsTo run tests for a specific module:
./run-tests.sh thunderbird_accounts.mail.testsAdditionally, you can run tests with coverage to help you identify functions that aren't covered by functional or unit tests.
To do so run the following command:
./runs-tests-and-generate-coverage.sh thunderbird_accountsThe data for coverage is stored in the coverage folder (which is volume mounted.) It generates a detailed html report and a text report to display in-console.
The html report is stored in coverage/htmlcov.
Please see the E2E tests README.
We run Flower to surface information about Celery tasks. This service exposes a web interface on
port 5555. In development, you can access this at http://localhost:5555 after bringing services
online with docker-compose. In Thunderbird's live (protected) environments, we have to create an
SSH proxy through a bastion.
How to build a bastion is documented in the Pulumi config files themselves. Once you have one that allows traffic from your IP, you'll need to gather some info:
- Your bastion's public IP address (
$BASTION_IP) - The DNS address for the Flower load balancer in the environment (
$FLOWER_LB_DBS) - An available local port to forward through, let's say
8443.
Open an SSH proxy to the bastion server:
ssh -L 8443:$FLOWER_LB_DNS:443 ec2-user@$BASTION_IPOur live environments all use TLS, so you will need to browse to https://localhost:8443/. You will have to push past an SSL certificate hostname mismatch alert, but then you will find yourself at the Flower landing page, listing Celery workers on the network.