Self-hosting

Clone, set a few env vars, run. The app works on your laptop with SQLite, and on Railway / any Python host with Postgres.

Local setup

git clone https://github.com/scripts-and-tables/emails-manager.git
cd emails-manager

python -m venv .venv
.venv\Scripts\activate            # Windows
# source .venv/bin/activate       # macOS / Linux

pip install -r requirements.txt

copy .env.example .env            # Windows
# cp .env.example .env            # macOS / Linux

Fill in .env (see the env vars table below), then:

python manage.py migrate
python manage.py runserver

The app runs at http://127.0.0.1:8000.

Generating keys

Both SECRET_KEY and FIELD_ENCRYPTION_KEY need to be set to real random values. Easy way:

# SECRET_KEY โ€” any long random string
python -c "import secrets; print(secrets.token_urlsafe(64))"

# FIELD_ENCRYPTION_KEY โ€” must be a Fernet key
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Rotating FIELD_ENCRYPTION_KEY invalidates every stored IMAP password. Keep it stable, or be prepared to re-enter every password.

Environment variables

Variable Required Notes
SECRET_KEY yes Django signing key. Long random string.
DEBUG no Defaults to False. Set True locally.
ALLOWED_HOSTS no Comma-separated. Defaults to localhost,127.0.0.1.
CSRF_TRUSTED_ORIGINS no Comma-separated origins for CSRF protection.
FIELD_ENCRYPTION_KEY yes Fernet key for IMAP password encryption. Rotating invalidates all stored passwords.
RESEND_API_KEY yes (for email) Without this, OTP and verification emails won't send.
RESEND_FROM_EMAIL yes (for email) Must use a verified domain in Resend. RFC 5322 format.
DATABASE_URL no Postgres URL. Falls back to SQLite if unset.

Project layout

emailsmanager/   # Django project (settings, root urls, wsgi)
core/            # Single app โ€” models, views, urls, templates, encryption, middleware
docs/            # This Pages site (you're looking at it)
requirements.txt
.env.example
manage.py

Deployment notes

Railway / RAILPACK gotcha

Static files in this project are served by WhiteNoise with WHITENOISE_USE_FINDERS = True, on purpose. Railway's RAILPACK builder runs collectstatic in a preDeploy phase whose filesystem doesn't propagate into the runtime container, so STATIC_ROOT ends up empty in production and WhiteNoise 404s every /static/ URL (including /static/admin/...). Finders mode works because both Django's contrib/admin/static and the app's core/static/ ship inside the runtime image as installed packages and repo source.

Database

Production deploys go through DATABASE_URL (parsed by dj-database-url). Anything without it falls back to a SQLite file in the project directory, which is fine for local dev and small one-user deploys.

Outbound email

Transactional email goes out through Resend. Verify your sender domain there, then point RESEND_FROM_EMAIL at any address on that domain (the app uses a dedicated verified-domain sender per a recent rebrand).

First admin user

Once the app is up:

python manage.py createsuperuser

Continuous integration

The repo ships a single GitHub Actions workflow at .github/workflows/ci.yml that runs on every push to main and every pull request:

  • Python 3.13, pip cache enabled.
  • ruff check . against the whole repo.
  • python manage.py test --verbosity=2.
  • Test-only env values are injected inline; no real secrets are used.
Spotted something missing? Open an issue or PR on GitHub.