Thunderbird Appointment: self-hosted scheduling for the Thunderbird Services team
Invite others to grab times on your calendar. Choose a date. Make appointments as easy as it gets.
At a glance
- What is it?
- Thunderbird Appointment is a booking service where invitees pick a slot from a subscriber's availability rather than negotiating times over email, with a Python backend, a web frontend, and a Docker Compose stack of five containers. It suits a team that wants to run its own scheduling page, and the detail to plan around is that the official development path is Docker only, with a first-run registration step that has to be enabled before any account exists.
- Who is it for?
- Adopt Thunderbird Appointment if you want to run your own booking page rather than depend on a hosted scheduling product, since the whole stack is in one repository under the Mozilla Public Licence.
- Can I use it commercially?
- Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The idea in one line
Thunderbird Appointment invites others to grab times on your calendar. Choose a date. Make appointments as easy as it gets.
That is the whole product described in three short sentences, and the third one is the interesting claim. The problem the service solves is the back-and-forth of scheduling: one person proposes times in an email thread, the other replies with their own three, and a day later somebody has to pick one. The service inverts it. The subscriber publishes their availability, the invitee picks a slot from it, and the negotiation never happens.
The two screenshots in the README show the two sides of that. There is the bookers page for attendees and the dashboard for subscribers, and then three more covering availability setup, managing bookings and general settings. So the subscriber publishes rules and handles incoming requests, and the attendee is a one-page flow.
The distinction between the two audiences matters for how the service is built. A subscriber is a person with a calendar and a set of rules about when they are available, and a booker is a person picking a time from those rules. The project calls the subscriber's account a subscriber in the first-time registration flow and uses the `subscriber` table as the example of a table the database creates on first run, so the vocabulary is consistent throughout.
The project is the Thunderbird Services team's own scheduling tool, published under the Mozilla Public Licence 2.0, written in Python with a separate frontend, and hosted at appointment.tb.pro. The topic tags name the domain precisely: appointment-booking, appointment-scheduling, appointments-manager, calendar, scheduling and thunderbird-pro.
The releases follow a build-number convention rather than semantic versioning. The three most recent are r-0948 for v1.9.2 from 2026-10-16, r-0942 for v1.9.1 from 2026-10-08 and r-0940 for v1.9.0 from 2026-10-06, so the release tag carries both a running build number and the application version. The last push was on 2026-10-16 and the default branch is main.
Getting it running, which means Docker
The Get started section is unusually direct about the supported path. Using Docker is the recommended and for now the only supported method of building and developing Thunderbird Appointment. A detailed technical documentation of the application architecture can be found in the docs folder, which is still work-in-progress.
The installation is four steps. The first is to get the files and create the environment files from the examples:
git clone https://github.com/thunderbird/appointment
cd appointment
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.envTwo separate environment files, one for each side of the application, and both created by copying an example rather than by writing them from scratch. That is the right pattern for an application with a frontend and a backend, since the frontend's build-time configuration and the backend's runtime configuration are different things and would otherwise end up sharing a file.
The second step is the one that trips people up. The `backend/.env` file has to contain `APP_ALLOW_FIRST_TIME_REGISTER=True`, which enables the creation of your first admin user, and if you want to access admin-only pages you add your account's email address to the `APP_ADMIN_ALLOW_LIST` env variable.
The third step is to build and run:
docker-compose up -d --buildwhich creates and starts 5 different containers: backend, frontend, postgres, redis and mailpit. The README then lists the addresses: the frontend at http://localhost:8090, the backend at http://localhost:5000, the PostgreSQL database at localhost:5435 with the username and password set to `tba` and `abcd%efgh`, the OpenAPI docs at http://localhost:5000/docs or http://localhost:5000/redoc, and the Mailpit docs at http://localhost:8026.
The OpenAPI endpoint is worth noting for anyone who wants to script against the service rather than use its interface, since a booking service is the kind of thing you might want to integrate with a form or an internal tool.
The fourth step is to check the containers are running. On first run the database will initialize and a first time setup command will be triggered, and going forward database migrations will automatically run on `docker-compose up`. You check that the database contains tables, for example the `subscriber` table, which is still empty at this point.
That last sentence is a useful diagnostic: an empty `subscriber` table with the schema present means the migration ran and no one has registered yet.
The compose file shows the real architecture
The docker-compose.yml in the repository root is more informative than the README, because it names the processes rather than just the endpoints.
The backend service is defined once with a YAML anchor, `&backend`, and the three Celery services derive from it with the merge key, `<<: *backend`. That single trick tells you the shape of the application: one API process, and three background worker processes that differ only in their role and their configuration.
The backend runs with `CONTAINER_ROLE=api`, `IS_LOCAL_DEV=yes` and `RELEASE_VERSION=localdev`, and it depends on postgresql with the condition `service_healthy`. The health check on the database is a `pg_isready` against the configured database and user, with a five second interval and 55 retries, and the postgres image is version 17 with a 128mb shared memory size.
The three Celery services are the worker, the beat scheduler and flower. The worker overrides the environment with `CONTAINER_ROLE=worker` and `CELERY_EMBED_BEAT=false` and depends on the backend and on redis. The beat service is the periodic scheduler, `CONTAINER_ROLE=beat`, also depending on the backend and redis. The flower service is the monitoring UI, `CONTAINER_ROLE=flower`, mapped to port 5556, and it is configured with `FLOWER_UNAUTHENTICATED_API=true`.
That last setting deserves a note, because it is a local development convenience that would be a problem in production. Flower is the Celery monitoring dashboard, and leaving its API unauthenticated means anyone who can reach that port can inspect the task queue and the worker state. The frontend service shows the other side of the local development posture, mounting the whole frontend directory and an anonymous volume over `/app/node_modules` with `WATCHPACK_POLLING=true`, so that editing frontend files reloads without a rebuild.
The Redis service is not plain Redis but `redis/redis-stack`, and it exposes two ports: 6379 for Redis itself and 8070 for what the compose file labels as Insights. The mailpit container is the email catcher, so that a development install sends real mail through a local SMTP sink rather than out to the internet.
The backend service also adds `extra_hosts` with `host.docker.internal:host-gateway`, which is how a container on Linux reaches a service running on the host. That entry is there for a reason and it is the kind of detail that only appears once someone has needed it.
Authentication, and the first-run problem
The authentication section is short and the reason it exists is a specific one.
Appointment includes simple password-based authentication. This is meant for developing and testing Appointment, but can also be used when self-hosting the app.
So there is no forced single sign-on for a self-hoster. The password login is legitimate as a production mechanism for your own deployment, which is a different statement from it being the mechanism Thunderbird themselves use.
The first-run flow is worth understanding because it explains the two environment variables from the installation steps. When you access the frontend the first time, you see a first-time-user login form. You enter the email address you configured in your allow list and a password. This login will create the first user, also called a subscriber in Appointment, granting you access to the application. Any login attempts with other email addresses after that will check against existing credentials.
So the order is: enable first-time registration, log in with the email on the allow list, and the account is created as a side effect of that first successful login. After that, the allow list is no longer what governs access, because credentials exist.
The allow list exists so that the first account cannot be claimed by whoever reaches the site first. On a public deployment, an open first-time registration is a race, and the allow list closes it. On a private deployment it is harmless.
For Thunderbird Services themselves, the situation is different, and the README points at it. Thunderbird Accounts is their own OIDC provider, and there is a note that if you are starting fresh with Thunderbird Accounts you should review its documentation on setting it up, including a section on before you begin.
The pattern is worth noting: a self-hosted deployment gets password login out of the box, and an organisation that runs its own identity provider swaps in OIDC. The service supports both because it is used by both.
How it is built, localised and released
The repository layout shows a three-part project with its own documentation and its own test suite.
There is a `backend/` directory, a `frontend/` directory and a `test/` directory, and the test directory is separate from the application code, which is the normal arrangement for end-to-end tests. The contributing section points at three separate readmes: the Backend Readme, the Frontend Readme and the E2E Tests Readme. And the request is simple: contributions are very welcome, please lint/format code before creating PRs.
The `docs/` directory holds the technical documentation of the application architecture, which is described as still work-in-progress. The `legal/` directory is there for licence and privacy text, the `pulumi/` directory for infrastructure as code, and `.vscode/` for editor settings.
Localisation uses Fluent, the localisation system from Mozilla, with the files located in their respective `l10n/<locale>/*.ftl`. That is a per-application directory structure rather than a single shared one, so the backend and the frontend each carry their own locale files, and the `.ftl` format is Fluent's own flat translation format.
The deployment process is a two-stage promotion. When changes are merged to main, a new release is cut and the changes are deployed to Appointment Staging. After you have checked staging and it is ready to push to production, you edit the release entry and press the Publish release button.
That manual gate on production is worth noting for anyone who wants to contribute. The path from a merged pull request to production runs through a staging environment and a human decision, not an automatic deploy, and the release entry is edited by hand before publishing. For a service handling calendar bookings, that kind of pause is reasonable.
The release naming ties back to the build numbers. The recent tags, r-0948 for v1.9.2 and r-0942 for v1.9.1, put a running count in front of the version, which is what lets a deployment identify exactly which build is running.
For feedback and support, the project uses a Topicbox group rather than the GitHub issue tracker, and the repository has a code of conduct alongside the licence file. The OpenAPI documentation exposed by the backend, at the `/docs` and `/redoc` paths, is the reference for the service's HTTP surface.
Editorial conclusion
Adopt Thunderbird Appointment if you want to run your own booking page rather than depend on a hosted scheduling product, since the whole stack is in one repository under the Mozilla Public Licence. Do not expect to develop it outside Docker, because that is stated as the recommended and for now the only supported method, and check first how you intend to handle authentication, because the built-in password login is described as suited to development and testing while the Thunderbird Services deployment uses their own OIDC provider.
Frequently asked questions
What is Thunderbird Appointment?
It is an appointment booking service where invitees pick a slot from a subscriber's published availability instead of negotiating times over email. It has a bookers page for attendees and a dashboard for subscribers, plus availability setup, booking management and settings, and is licensed under MPL-2.0 with the hosted instance at appointment.tb.pro.
How do I run Thunderbird Appointment locally?
Clone the repository, copy the example environment files with `cp backend/.env.example backend/.env` and `cp frontend/.env.example frontend/.env`, set `APP_ALLOW_FIRST_TIME_REGISTER=True` in the backend file and add your email to `APP_ADMIN_ALLOW_LIST`, then run `docker-compose up -d --build`. That starts five containers: backend, frontend, postgres, redis and mailpit.
What are the ports and services in the Thunderbird Appointment development stack?
The frontend is at http://localhost:8090, the backend at http://localhost:5000 with OpenAPI docs at /docs and /redoc, PostgreSQL at localhost:5435 with the credentials tba and abcd%efgh, Mailpit at http://localhost:8026, and Redis on 6379 with Insights on 8070. The API process and the three Celery roles are defined from a single compose anchor.
How does authentication work in Thunderbird Appointment?
There is simple password-based authentication, described as meant for developing and testing but also usable when self-hosting. The first login creates the first user, also called a subscriber, and only the email addresses on the allow list can do that. For Thunderbird Services, the deployment uses their own OIDC provider, Thunderbird Accounts.
Does Thunderbird Appointment support multiple languages?
Yes. The project uses Fluent for localisation, with the translation files located in their respective l10n/<locale>/*.ftl directories, so the backend and the frontend each carry their own locale files in the Mozilla Fluent format.
Is Thunderbird Appointment still being maintained?
The repository is not archived and the last push was on 2026-10-16. The releases use a running build number in front of the version, with r-0948 for v1.9.2 the most recent, and changes merged to main are deployed to a staging instance before the release entry is edited and published to production.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/thunderbird-appointment)Was this useful?