ApprovalFlow
A self-hosted approval-workflow app. Requests are created on the site (login required), move through each workflow's ordered chain of approvers by email, and once approved or denied, everyone involved up to that point is notified. Every request keeps a full audit log of everything that happened to it.
Outgoing mail is preconfigured for Purelymail — you only provide the account's login and password.
What it does
- Sequential multi-step approval. Every workflow has an ordered chain of approvers. The request goes to the first person; each approval sends it on to the next; the last approval completes it. A denial at any step stops the workflow immediately — nobody later in the chain is contacted.
- Decisions notify everyone. On the final outcome (approved or denied), the requester and every approver involved up to that point receive the outcome email.
- Full log. Every request records: received, each approval request sent, each step's decision, notifications, and any mail errors — each with a timestamp.
- Attachments. A request can include one uploaded file (up to 15 MB). It is attached to every approval mail sent along the chain, and admins can download it from the request page. Files are stored next to the database in
data/attachments/. - Mail status page (admin only). Every outgoing email is recorded with its delivery result and whether the Sent-folder copy succeeded, so you can confirm mail is going out and spot errors at a glance.
- Multiple named workflows, each personalized: its own approval chain and the wording of all three emails (approval request, approved notice, denied notice) with
{placeholders}. - Main page (login required): users pick a workflow they have access to and submit a request from the browser.
- Admin pages: create/edit/pause workflows, monitor all requests and open their full logs, decide directly from the UI, manage users, and set which users can access which workflows.
Quick start (Docker)
cp .env.example .env # edit it: SECRET_KEY, BASE_URL, MAIL_USER, MAIL_PASSWORD
docker compose up -d
Open http://localhost:8000 and sign in with ADMIN_USERNAME / ADMIN_PASSWORD from .env (defaults admin / changeme — change them). The SQLite database lives in ./data/.
Quick start (bare Python)
pip install -r requirements.txt
cp .env.example .env
set -a; source .env; set +a # or export the variables another way
python app.py
Leaving MAIL_USER empty prints outgoing mail to the console instead of sending it — handy for trying the app before entering the account.
Mail server
The app is preconfigured for Purelymail's servers — you never enter host or port settings:
| Outgoing (SMTP) | smtp.purelymail.com, port 465, SSL/TLS |
| Sent-folder copy (IMAP) | imap.purelymail.com, port 993, SSL/TLS |
Every email the app sends is also saved as a copy to the account's Sent folder over IMAP, so the mailbox keeps a complete record of what went out.
Only two settings are asked for in .env: MAIL_USER (the email address) and MAIL_PASSWORD. If the account uses Two Factor Authentication, use an App Password instead of the real password.
The request lifecycle
web form (login required)
│ received + logged
▼
approval request → approver 1 ── deny ──► STOP: DENIED notice to
│ approve everyone involved so far
▼
approval request → approver 2 ── deny ──► STOP: DENIED notice ...
│ approve
▼
⋮ (… every approver in the chain, in order)
▼
last approver approves ──► APPROVED notice to everyone involved
(requester + all approvers)
Each step gets a fresh secret link, so an earlier approver's link cannot decide later steps.
Personalization placeholders
Usable in every workflow's subject and body templates:
{workflow}, {requester}, {subject}, {body}, {request_id}, {step}, {total_steps}, {decided_by}, {summary} (creation + each approval with timestamps, for the approved notice), {deny_reason}, {approve_url}, {deny_url}.
Security notes
- Set a strong
SECRET_KEYand put the app behind HTTPS (a reverse proxy such as Caddy or nginx) — the approve/deny links are secrets. - Decision links are unguessable random tokens and work only once; a second click shows "already decided."
- Passwords are stored hashed (Werkzeug PBKDF2). Change the bootstrap admin password immediately.
- Non-admin users only ever see and use the workflows they've been granted.
Project layout
app.py Flask routes, auth, admin pages
db.py SQLite schema + audit/mail-log helpers
pipeline.py approval chain + decision/notification logic
mailer.py Purelymail SMTP sending, Sent-folder copy, mail log
templates/ pages static/style.css styling