2026-09-13 20:03:24 +01:00
2026-09-13 20:03:24 +01:00
2026-09-13 20:03:24 +01:00
2026-09-13 20:03:24 +01:00
2026-09-13 20:03:24 +01:00
2026-09-13 20:03:24 +01:00
2026-09-13 20:03:24 +01:00
2026-09-13 20:03:24 +01:00
2026-09-13 20:03:24 +01:00
2026-09-13 20:03:24 +01:00
2026-09-13 20:03:24 +01:00
2026-09-13 20:03:24 +01:00
2026-09-13 20:03:24 +01:00
2026-09-13 20:03:24 +01:00

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 or more uploaded files (15 MB combined). They are attached to every approval mail sent along the chain, and admins can download each from the request page. Files are stored next to the database in data/attachments/.
  • Knowledge chain. Next to each workflow's approval chain there is a knowledge chain: addresses that never approve anything but are informed by e-mail when a request is created and on every step approval, denial, or final approval. They are not counted as "involved" and don't receive the participant outcome notice — only their own FYI messages, which include the request's attached files just like the approval mails.
  • Configure profile. Every user has a profile page (top-right corner): first name, last name, unit location, e-mail, change password, and MFA. The profile e-mail pre-fills the mandatory "Request's e-mail" field when submitting a request.
  • MFA. Any user can enable two-factor authentication from their Configure profile page (TOTP — Google Authenticator, Authy, 1Password, …). Sign-in then requires password + 6-digit code. Admins can disable MFA for a user who lost their device.
  • Light and dark mode. A toggle in the top bar (and on the login page) switches themes; the choice is remembered per browser and defaults to the system preference.
  • Images in emails. Admins upload PNG/JPG/GIF images (up to 2 MB each) in the library shown below the Email wording section of any workflow. Placing an image's {image:name} tag in the approval request body, approved notice body, or denied notice body embeds the image at that spot (HTML email with a plain-text fallback).
  • 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/.

If a value in .env contains a $ (common in passwords), escape it as $$ — e.g. MAIL_PASSWORD=xK9$$vh4pQ2. Docker Compose otherwise treats $word as a variable and silently replaces it with a blank string, printing a warning like The "word" variable is not set.

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}.

Upgrading an existing instance (no data loss)

All schema changes are applied automatically at boot by the built-in migrations — the SQLite database and uploaded files live in ./data/, which is a volume and is never touched by rebuilding the image.

cd /path/to/approvalflow
docker compose down                        # stop the app (data stays in ./data)
cp -r data data.backup-$(date +%F)         # safety backup of DB + attachments
# replace the code with the new version, KEEPING .env and data/:
#   unzip -o approvalflow.zip -d ..        (or copy the new files over the old ones)
docker compose up -d --build               # rebuild image, start; migrations run at boot
docker compose logs | grep migrate         # optional: see what was migrated

Roll back = stop, restore data.backup-… to data/, start the previous image.

Security notes

  • Set a strong SECRET_KEY and 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
S
Description
No description provided
Readme
328 KiB
Languages
Python 58.6%
HTML 31%
CSS 10%
Dockerfile 0.4%