Open-source project
ZeroPointSix/outlookEmailPlus avatar
ZeroPointSix/outlookEmailPlus

OutlookMail Plus: the mailbox pool built for signup verification

Outlookmail Plus: designed specifically for registration.

2,034 stars412 forksPythonApache-2.0

At a glance

What is it?
OutlookMail Plus is a Flask mailbox manager for registration work, where pooled addresses, temporary Cloudflare Worker mailboxes and OAuth-imported Outlook accounts feed verification codes into browser signup flows. The pooling and extraction logic is carefully specified, while the version metadata inside the repository is not.
Who is it for?
Adopt OutlookMail Plus when your work sits inside browser signup flows and you want one place to claim an address, read the verification mail and hand the code to an external script. Leave it alone when you need a general mail client, an outbound mail path, or an audit of what was sent.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 60 days 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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A mailbox manager scoped to registration and verification

OutlookMail Plus exists for one narrow job: keeping a registration mailbox usable while a browser signup flow runs. The project presents itself as a mailbox manager for individuals and teams, and the interface follows from that. You copy the address, trigger a message from the target site, come back, pull the newest mail, then apply a regular expression to lift out the verification code or the verification link.

Sending was dropped on purpose as a non-core capability, so there is no message window and no outbound path to audit. What remains is reading, extraction, pooling and notification, with Telegram push for mailboxes marked important and a webhook channel added alongside email notifications.

Where the addresses come from stays open. Outlook accounts arrive through an OAuth import, Gmail, QQ, 163 and any custom IMAP server work over plain IMAP, and the built-in CF Worker temporary mailbox adds throwaway domains whose Admin Key is written to the database behind an `enc:` prefix. Since v2.2.0 that temporary side runs as a plugin system with four providers, Cloudflare, Custom, GPTMail and Moemail, installed and configured at runtime, with provider settings decoupled from domain selection.

One deployment detail is public rather than hidden. The demo site publishes its login password in the same file, `12345678`, in front of ten accounts whose data is reset periodically, which is fine for a sandbox and is not a hardening posture. An environment variable can hide the password change entry so visitors cannot repoint the admin login from the settings page.

A flat tree of controllers, templates and SQLite state

The code layout is flat and matches that scope.

text
outlook_web/          Flask 应用主体(controllers / routes / services / repositories)
templates/            页面模板
static/               前端脚本与样式
data/                 SQLite 数据与运行时文件
tests/                自动化测试
web_outlook_app.py    兼容入口

Controllers, routes, services and repositories sit in one package, pages come from templates, and SQLite plus runtime files live under the mounted data directory. `web_outlook_app.py` stays as a compatibility entry point, so an older start command keeps working after the move into the package.

The dependency list is short enough to read in one pass, and it prices each feature. `flask-cors` exists for the browser extension, `cryptography` protects the CF Worker Admin Key, `bcrypt` handles the login password, `APScheduler` with `croniter` carries scheduled jobs, and the `docker` package is needed only for the self-update path described further down.

Reading is synchronous under Gunicorn, which is why the compose file keeps a single worker and uses threads instead: more workers would each start another copy of the background scheduler. That constraint is written into the shipped defaults rather than left to the operator.

Claiming, returning and reusing addresses in the pool

What separates this from a mailbox viewer is the pool, and the claim API is where the interesting semantics live. Accounts move through claim, complete, cooldown recovery and expiry reclaim. A caller that passes `project_key` together with `caller_id` and `task_id` opts into per project isolation: once an address has succeeded, the same project will not be handed that account again, and after `claim-complete(result=success)` the record returns to `available` so a different project can take it right away.

Leave those three parameters out and the older semantics stay, and the same holds for `provider=cloudflare_temp_mail` and for the temporary mailbox path. That split is deliberate. Long lived accounts behave like inventory that recycles, while throwaway CF Worker mailboxes behave like consumables, and `claim-random` still creates a CF mailbox on the fly when the pool runs dry, which is what keeps an automated signup path moving at volume.

Each change arrived with a schema version, v17 for the isolation key and v22 for the success reuse path, so an existing install is expected to carry its migrations forward. The README does not document how to reverse those migrations after they have run.

The X-API-Key surface for external callers

Automation is meant to run against a real key rather than a shared admin login. The external interface authenticates with `X-API-Key`, and the admin side manages several keys at once, one per calling system, so a script that needs only a few addresses does not inherit the key that owns the whole pool. Per mailbox scope authorisation, an IP allowlist and rate limiting sit on the same surface.

That is the minimum you want standing in front of anything that fires automatic signups, and the pool document at the repository root, `registration-mail-pool-api.en.md`, treats the HTTP interface as a product of its own rather than an afterthought of the web screens.

The browser extension leans on those same keys instead of a session cookie. v2.0.0 added `browser-extension/` for Chrome and Edge on Manifest V3, where claiming an address, extracting the code and then completing or handing the address back all happen inside the signup tab, and the backend picked up `chrome-extension://` CORS support to allow it. Version 2.1.0 later traced a real cause behind the extension reporting an invalid key, a mismatch between copying a masked API Key and the external pool prerequisites.

Polling was consolidated in the same period. v1.11.0 merged the standard and simple modes into a single `poll-engine` to fix a race and a backlog of stuck states, which tells you the earlier design had two code paths reading the same mailboxes.

Docker deployment and the two frontend image lines

Deployment is a container plus a .env file, and the two documented paths disagree about the host port. The short path in the README binds 5000 directly:

bash
docker run -d \
  --name outlook-email-plus \
  -p 5000:5000 \
  -v $(pwd)/data:/app/data \
  -e SECRET_KEY=your-secret-key-here \
  -e LOGIN_PASSWORD=your-login-password \
  -e ALLOW_LOGIN_PASSWORD_CHANGE=false \
  guangshanshui/outlook-email-plus:latest

Expect a listening service on 5000 and a writable data directory, since SQLite lives under `/app/data`. Setting `ALLOW_LOGIN_PASSWORD_CHANGE` to false keeps the settings page from rewriting the admin password.

The compose path in the repository differs. It maps host port 5001 onto the container's 5000 so a local service on 5000 does not collide, and it refuses to start unless `SECRET_KEY` is present:

yaml
    ports:
      - "${APP_PORT:-5001}:5000"
    env_file:
      - .env
    environment:
      SECRET_KEY: "${SECRET_KEY:?请在 .env 中设置 SECRET_KEY(必须稳定,用于加密数据库敏感信息)}"
      WATCHTOWER_HTTP_API_TOKEN: "${WATCHTOWER_HTTP_API_TOKEN:-outlook-mail-plus-watchtower-default}"
      GUNICORN_WORKERS: "${GUNICORN_WORKERS:-1}"
      GUNICORN_THREADS: "${GUNICORN_THREADS:-8}"
      GUNICORN_TIMEOUT: "${GUNICORN_TIMEOUT:-120}"

Two of those defaults deserve a second look. `WATCHTOWER_HTTP_API_TOKEN` falls back to a fixed literal on every deployment that leaves it empty, so replace it before the first start if the update endpoint matters to you. And `GUNICORN_THREADS` is what carries the synchronous mail reads, a load `GUNICORN_WORKERS` at 1 deliberately keeps single because the shipped comment ties extra workers to a background scheduler that would start twice.

The image choice is the other decision. Since `v2.9.10` the default tag carries the Ant Design Pro single page interface, while the lighter server rendered interface ships as a separate `-legacy` line sharing the same backend and database. `IMAGE_TAG` selects between them and defaults to `latest`.

Updates run from inside the container, which is convenient and widens the blast radius. Two paths exist: Watchtower, the recommended one, pulls new images and replaces the container, and the advanced Docker API mode asks the host daemon to recreate containers. The compose file ships that second path as a commented out `DOCKER_SELF_UPDATE_ALLOW` line with a warning attached, and the warning is the substance, since a reachable Docker socket lets the container drive other containers on that host.

Azure app registration traps inside the token tool

Registering the Azure app is where self-hosted setups stall, so the README maps each mistake to a string you actually see. When the supported account types cover organisational directories only, the request fails with `unauthorized_client`. When they cover personal Microsoft accounts only, the failure moves to the `/common` verification step before anything is written and reports `AADSTS9002331`. The working choice covers any identity provider or organisational directory plus personal accounts.

Three more traps follow from the same screen. If Azure rejects a switch of the account types with an invalid `api.requestedAccessTokenVersion`, the manifest needs that value set to 2. If a `client_secret` is still demanded while the public client switch is on, the callback is being read as a confidential web client, and the fix is to register under Mobile and desktop applications with a `http://localhost` redirect, then paste the callback URL into the tool by hand. `AADSTS70000` means the scope requested at authorisation and the scope checked at validation disagree, so force a fresh consent.

For Graph work the minimum set named is `offline_access` plus `Mail.Read` plus `User.Read`, with one more Office 365 Exchange Online permission when IMAP is needed. The shipped compose file keeps the backend fallback on the IMAP compatible scope in `OAUTH_SCOPE` while the interface defaults to the Graph mail preset, which is a sensible split: the backend stays compatible with older imports while new setups get the narrower Graph permissions. Tokens are verified before they are written into an account, and incompatible configurations are refused rather than stored.

Code extraction, alias backtracking and the extract log

Extraction is the reason most people stay, and v1.15.0 hardened it in two ways. Where the regular expression is not enough, a system level AI fallback can read the message, but it fires only when two attempts both come back with low confidence and the reply has to match a fixed JSON contract. A trigger that loose would quietly rewrite codes, so the double low confidence gate is the design.

Aliases are treated as first class mailboxes. A `+tag` address is recognised and the system backtracks to the base account, so a code delivered to an alias is not lost.

Observability came next. v2.1.0 replaced the old dashboard with five tabs and added the `verification_extract_logs` table, which puts ordinary accounts, temporary mailboxes and external API extractions on one chain instead of three. v1.19.0 made refresh failures structured, with an error code, runnable steps and a trace hint after a Selected batch failed early.

Version 2.2.0 shipped a local personal information generator inside the browser extension, alongside the full Jest coverage of it.

Where the version numbers and legacy tags disagree

Two contradictions in the files are worth stating plainly, because automation trips on them.

The first is the number in the tree. The README calls `v2.9.10` the current stable version and builds both image lines around it, yet package.json at the repository root declares version 2.1.0. The npm manifest exists to drive the browser extension tests, so it may simply lag, but any pipeline that reads a version out of it ends up well behind what the images actually run.

The second is the rollback note. The README says that pointing `IMAGE_TAG` at `v2.9.10-legacy` or any lower version restores the old interface, yet the image table names only `latest-legacy` and `v2.9.10-legacy`. No lower legacy tag appears anywhere in the documentation, so that parenthetical advice has nothing to point at.

Smaller gaps sit alongside. The bare test script in package.json prints a hint and exits non-zero on purpose, so a generic test run fails by design and `npm run test:browser-extension` is the scoped entry point. Python work is checked against 3.11 with a 127 character line length, and the root carries `test_pool_admin_ui.py` and `verify_issue49_governance.py` as standalone scripts.

The last push to the repository was on 2026-08-24. The code sits under the Apache-2.0 licence, while package.json declares its own license field as ISC, a mismatch to settle before anyone forks it for internal use.

Editorial conclusion

Adopt OutlookMail Plus when your work sits inside browser signup flows and you want one place to claim an address, read the verification mail and hand the code to an external script. Leave it alone when you need a general mail client, an outbound mail path, or an audit of what was sent. Before deploying, match the Azure app registration to the public client flow, replace the default Watchtower token with your own, and confirm that the legacy tag you intend to fall back on actually exists in the image table.

Frequently asked questions

What is ZeroPointSix/outlookEmailPlus for?

It is a mailbox manager for registration and verification work. You copy an address, trigger mail from a signup page, then pull the newest message and extract the code or link with a regular expression.

Does ZeroPointSix/outlookEmailPlus send email?

No. Sending was dropped as a non-core capability, and the project keeps reading, extraction, pooling and notification instead.

How do I run ZeroPointSix/outlookEmailPlus with Docker?

The README gives a docker run command that binds port 5000, and a compose file that maps host port 5001 onto the container's 5000 to avoid a local clash. The compose file refuses to start unless SECRET_KEY is set in .env, because that key encrypts sensitive rows in the database.

Which Azure app registration works with the OutlookMail Plus token tool?

Set the supported account types to cover any identity provider or organisational directory plus personal accounts. An organisational directory only registration fails with unauthorized_client, and a personal accounts only registration fails at the /common step with AADSTS9002331.

What does project_key do in the OutlookMail Plus mailbox pool?

Passing project_key with caller_id and task_id isolates claims per project and lets a successful account return to available for another project to reuse. Without those parameters, and for provider=cloudflare_temp_mail, the older semantics stay in place.

Can ZeroPointSix/outlookEmailPlus update itself?

Yes, through Watchtower, which the project recommends, or through a Docker API self-update mode marked as advanced. The compose file ships that mode commented out with DOCKER_SELF_UPDATE_ALLOW set to false, because enabling it lets the container drive other containers on the host.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. Releases
  5. ZeroPointSix/outlookEmailPlus on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/zeropointsix-outlookemailplus.svg)](https://hysenlabs.com/projects/zeropointsix-outlookemailplus)

Was this useful?