56 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
v0.6.3 (2026-04-07)
Bug Fixes
- lint: Specify explicit React version to fix ESLint 10 compatibility
(
efa553c)
Chores
-
ci: Bump docker/login-action from 3 to 4 (
6fe2f9a) -
deps: Bump @tanstack/react-query in /frontend (
466e852) -
deps: Bump @types/node from 25.5.0 to 25.5.2 in /frontend (
cc1edd8) -
deps: Bump aiosmtplib from 3.0.1 to 5.1.0 in /backend (
284fbba) -
deps: Bump alembic from 1.13.1 to 1.18.4 in /backend (
bd51a5b) -
deps: Bump email-validator from 2.1.0.post1 to 2.3.0 in /backend (
6f414e1) -
deps: Bump eslint from 9.39.4 to 10.2.0 in /frontend (
4bb92ac) -
deps: Bump eslint-config-next from 16.2.1 to 16.2.2 in /frontend (
9d53b11) -
deps: Bump faker from 22.6.0 to 40.12.0 in /backend (
9553fae) -
deps: Bump google-auth-httplib2 from 0.3.0 to 0.3.1 in /backend (
233595e) -
deps: Bump google-auth-oauthlib from 1.2.0 to 1.3.1 in /backend (
3bb7a7c) -
deps: Bump lucide-react from 0.577.0 to 1.7.0 in /frontend (
bfaa639) -
deps: Bump next from 16.2.1 to 16.2.2 in /frontend (
a5d8700) -
deps: Bump python-multipart from 0.0.22 to 0.0.24 in /backend (
f479974) -
deps: Bump sqlalchemy from 2.0.48 to 2.0.49 in /backend (
6283d1c) -
deps: Bump stripe from 14.4.1 to 15.0.1 in /backend (
582311a) -
deps: Bump uvicorn from 0.42.0 to 0.43.0 in /backend (
b2d044d)
v0.6.2 (2026-04-05)
Code Style
- Reformat test_gdpr.py and test_gmail_labels.py to fix black CI check
(
7afa33f)
[Unreleased]
Added
- Expanded
gmail_service.pyunit test coverage: 10 new tests coveringinject_emailHttpError branch,get_or_create_label(existing/create/HttpError/generic-error),inject_debug_emailfull flow,get_refreshed_token(changed/unchanged), andserviceproperty lazy initialization — 24 total tests intest_gmail_service.py - Expanded
api.tstest coverage from 20% to near-complete: 56 new tests covering all 10 API objects (authApi,userApi,mailAccountsApi,processingRunsApi,gmailApi,smtpApi,adminApi,notificationsApi,adminNotificationsApi,versionApi) — 65 total tests inapi.test.ts - Unit tests for
AddMailAccountModalcomponent (AddMailAccountModal.test.tsx): 28 tests covering create/edit mode rendering, provider wizard flow, form field changes, checkbox toggles, auto-detect, test connection, submit mutations, error extraction, and modal close interactions - Unit tests for provider endpoints (
test_providers.py): 23 tests covering provider presets, Gmail credential CRUD, import labels, authorize URL, debug email, and OAuth callback - Unit tests for mail account endpoints (
test_mail_accounts.py): 30 tests covering CRUD, toggle, pull-now, test connection, auto-detect, processing runs, and processing logs - Unit tests for authentication endpoints (
test_auth.py): 22 tests covering register, login, Google OAuth, authorize-url, and helper functions - Backend test coverage expanded (+150 tests, 361 → 511 total): added
test_gdpr.py(GDPR masking utilities),test_gmail_labels.py(Gmail label helpers),test_notification_service.py(Apprise notification service),test_auth_service.py(OAuth service token exchange and token creation),test_version_endpoint.py,test_auth_endpoints.py(register, login, Google OAuth, authorize-url, domain helpers),test_users_endpoints.py(profile CRUD, SMTP config upsert/delete),test_notifications_endpoints.py(full CRUD + test-send),test_logs_endpoints.py(processing-runs pagination/filtering + run log retrieval), andtest_app_settings_endpoints.py(list, upsert, delete, seed-defaults with bootstrap key guards).
v0.6.1 (2026-04-05)
Bug Fixes
- Upgrade pytest-asyncio to 1.3.0 for pytest 9.x compatibility
(
39052af)
Chores
-
ci: Bump actions/checkout from 4 to 6 (
68a6163) -
ci: Bump codecov/codecov-action from 5 to 6 (
594e380) -
ci: Bump docker/build-push-action from 5 to 7 (
2c26c15) -
ci: Bump docker/metadata-action from 5 to 6 (
0e87da8) -
ci: Bump mikefarah/yq from 4.44.6 to 4.52.5 (
f4c8e4a) -
deps: Bump @tanstack/react-query in /frontend (
348c347) -
deps: Bump @types/node from 20.19.30 to 25.5.0 in /frontend (
917dacc) -
deps: Bump aiohttp from 3.13.3 to 3.13.4 in /backend (
d919939) -
deps: Bump apprise from 1.7.1 to 1.9.9 in /backend (
094b1e1) -
deps: Bump axios from 1.13.6 to 1.14.0 in /frontend (
3b580c3) -
deps: Bump bcrypt from 4.3.0 to 5.0.0 in /backend (
f0213a0) -
deps: Bump celery from 5.6.2 to 5.6.3 in /backend (
5992a9e) -
deps: Bump httpx from 0.26.0 to 0.28.1 in /backend (
c50cf6e) -
deps: Bump next from 16.1.7 to 16.2.1 in /frontend (
ec90cd6) -
deps: Bump prometheus-client from 0.19.0 to 0.24.1 in /backend (
77c8426) -
deps: Bump pytest from 7.4.4 to 9.0.2 in /backend (
4a6f93e) -
deps: Bump python-dotenv from 1.0.0 to 1.2.2 in /backend (
4dbe30b) -
deps: Bump redis from 7.3.0 to 7.4.0 in /backend (
b914ac0) -
deps: Bump typescript from 5.9.3 to 6.0.2 in /frontend (
726e089) -
deps: Bump uvicorn from 0.27.0 to 0.42.0 in /backend (
ac6a837)
Documentation
-
Fix CHANGELOG entry to only reference pytest-asyncio bump (
0521c97) -
Fix secret key generation syntax in README quickstart (
9c12555) -
Rewrite README focused on Google 2026 POP/Gmailify shutdown replacement (
06efeed)
[Unreleased]
Changed
- Bumped
pytest-asynciofrom 0.23.3 to 1.3.0 to restore compatibility with pytest 9.0.2 in the backend test suite.
v0.6.0 (2026-03-29)
Chores
- Initial plan for frontend test coverage
(
7dd0b5f)
Features
-
Add frontend test coverage for date-utils, api interceptors, AuthGuard, QueryProvider, DashboardLayout (
63839c3) -
Add NotificationWizard + ProviderWizard tests, fix lint, update docs (
769907a)
v0.5.1 (2026-03-28)
Bug Fixes
- Convert jest.config.js to jest.config.mjs (ES module syntax)
(
0a0836e)
[Unreleased]
Changed
- README rewrite: Replaced README with a focused, marketing-oriented page targeting users affected by Google's 2026 shutdown of Gmail POP import ("Check mail from other accounts") and Gmailify. Fixed broken CI badge links (pointed to non-existent workflow files; now correctly references
ci.yml). Removed verbose technical implementation sections in favour of a clear problem statement, comparison table, 5-minute quick-start, and concise delivery-method summary.
Added
-
Celery tasks test coverage: Added 40 unit tests for
backend/app/workers/tasks.py, raising coverage from 9.22% to 96%. Tests cover_as_utchelper,process_mail_account(Gmail API and SMTP forwarding, credential revocation, empty-email detection, error handling),process_all_enabled_accounts(stale-run cleanup, interval checking), andcleanup_old_logs(data retention, stale-run recovery). -
Admin endpoint test coverage: Added 87 unit tests for
admin.pycovering all 17 endpoints (stats, user CRUD, plan CRUD, notification config CRUD, notification testing, processing runs/logs with GDPR masking and pagination). Coverage improved from 24% to 100%. -
Domain-based logo fallback for mail accounts:
ProviderLogoBannernow shows provider logos even for accounts that have noprovider_nameset, by extracting the domain from the email address and matching it against a newDOMAIN_ICON_MAP. Covers Gmail, GMX, WEB.DE, Yahoo Mail, AOL, T-Online, Outlook/Hotmail, IONOS, Freenet, iCloud, Posteo, and Proton Mail. -
Frontend test coverage: Added 113 new tests across 7 new test suites covering all components, utility functions, and API interceptors. Installed
@testing-library/react,@testing-library/jest-dom, and@testing-library/user-event. New suites:date-utils.test.ts(30 tests),api.test.ts(9 tests),AuthGuard.test.tsx(6 tests),QueryProvider.test.tsx(2 tests),DashboardLayout.test.tsx(14 tests),NotificationWizard.test.tsx(32 tests),ProviderWizard.test.tsx(20 tests). Total frontend: 119 tests across 8 suites. -
Improved
mail_processor.pytest coverage: Added 46 unit tests for POP3 connection testing, POP3 email fetching, IMAP edge cases (stale UID store failure, delete failure, MailFetchError re-raise, star-prefix line filtering), email forwarding (STARTTLS/SSL/multipart), and routing methods. Statement coverage increased from ~42% to 98%.
Fixed
- Test fixtures: Fixed
conftest.pyadmin user fixture using non-existentis_admin/is_verifiedfields (should beis_superuser), and JWTsubclaim using email instead of user ID. - Convert
frontend/jest.config.jstojest.config.mjsusing ES moduleimport/exportsyntax to resolve ESLint@typescript-eslint/no-require-importserror. - Suppress noisy
ignored untagged responseINFO log lines fromaioimaplibin Celery workers by setting theaioimapliblogger to WARNING level incelery_app.py. - Eliminate
file_cache is only supported with oauth2client<4.0.0warnings by passingcache_discovery=Falsetogoogleapiclient.discovery.build()ingmail_service.py.
v0.5.0 (2026-03-28)
Features
- ci: Add Codecov integration with frontend Jest coverage and CODECOV_TOKEN
(
0645bc0)
v0.4.5 (2026-03-28)
Bug Fixes
- ci: Replace safety scan with pip-audit to fix CI EOF error
(
8152635)
[Unreleased]
Added
- Codecov integration: frontend Jest test coverage (lcov) and backend pytest-cov (XML) are now uploaded to Codecov with
CODECOV_TOKENauthentication and separatefrontend/backendflags.
Fixed
- CI: replaced
safety scan --json(requires interactive login in Safety CLI v3) withpip-auditto fix EOF error in the security scan job
v0.4.4 (2026-03-28)
Bug Fixes
- Show proper error message instead of [object Object] in mail account wizard
(
191fee9)
v0.4.3 (2026-03-28)
Bug Fixes
-
Apply parseUTC to dashboard, unify date helpers in date-utils.ts (
db3ce61) -
Drop empty emails, clear stale errors on success, harden IMAP extraction (
0720901)
[Unreleased]
Security
- Upgraded
fastapifrom0.109.1to0.135.2, pulling instarlette>=1.0.0and fixing 4 Denial-of-Service vulnerabilities present instarlette<=0.35.1. - Updated CI security scan command from deprecated
safety checktosafety scan. - Added
.safety-policy.ymlto document and suppress the two unfixableecdsaside-channel CVEs (64396, 64459) that the upstream maintainers have acknowledged cannot be resolved in pure Python.
Fixed
-
Add mail account wizard showed
[object Object]— FastAPI validation errors returndetailas an array of objects, not a plain string. The frontend error handler now inspects the type ofdetail: if it is a string it is used directly; if it is an array the individualmsgfields are joined; otherwise the genericError.messageor a fallback string is shown. The fix is applied to both the Save and Test-Connection error paths. -
IMAP: fix all IMAP emails appearing empty —
aioimaplibstores RFC822 literal data asbytearray, notbytes. The FETCH extraction loop was checkingisinstance(line, bytes)which returnsFalseforbytearray, causing every email body to be silently skipped and the message to appear empty. The check now accepts both types (isinstance(line, (bytes, bytearray))) and converts the result to plainbytesbefore returning, so the rest of the pipeline is unaffected. This affected every IMAP account (T-Online, GMX, and others). -
IMAP: fix T-Online BYE "Too many invalid IMAP commands" —
UID STOREflag arguments now use RFC 3501–required parentheses:+FLAGS (\Seen)and+FLAGS (\Deleted). Strict servers such as T-Online reject the bare+FLAGS \Seensyntax as aBADcommand and forcefully drop the connection. -
IMAP: fix
aioimaplibcrash onUID SEARCH—aioimaplib's.uid()wrapper explicitly blocks"search", raisingcommand UID only possible with COPY, FETCH, EXPUNGE (w/UIDPLUS) or STORE. The initial UNSEEN discovery now uses a plainSEARCH UNSEENto obtain sequence numbers, followed by a lightweightFETCH (UID)to resolve them to stable UIDs; all subsequent operations (FETCH,STORE) continue to use the UID form. -
IMAP: filter whitespace-only RFC822 responses — the FETCH extraction loop now checks
email_data.strip()so that trivially-empty byte payloads (e.g.\r\n) returned by servers like T-Online are rejected at the extraction layer rather than being forwarded as empty emails. -
Tasks: drop completely empty emails with a warning — after parsing each fetched email, the processing loop now checks whether the message has no subject, no From header, and no body. Such emails are silently dropped (logged as
WARNING, written to the processing log, UID recorded as seen so the message is not retried). This handles cases where T-Online or other servers emit genuinely empty RFC822 responses that may indicate a server-side bug. -
Status page: clear stale IMAP errors after a successful run — on a successful processing run (
emails_failed == 0), the account'slast_error_messageandlast_error_atfields are now cleared. Previously a transient IMAP error would remain visible on the dashboard even after subsequent pulls completed without problems. -
Dashboard: fix UTC hour-shift on "Last check" timestamps — the dashboard page was using
new Date(iso)which treats timezone-naive ISO strings from the backend as local time, shifting relative labels (e.g. "1h ago" instead of "Just now") for users outside UTC. The dashboard now uses the sameparseUTC()helper that the logs page already applied. The helper functionsformatRelative,formatDate, andformatDurationhave been consolidated intosrc/lib/date-utils.tsand are imported by all pages.
v0.4.2 (2026-03-28)
Bug Fixes
-
CI pipeline Node.js 20 deprecation, Codecov input, img lint errors (
40c23c4) -
Suppress bandit B104 false positive for 0.0.0.0 binding in config.py (
c092dd9)
Code Style
- Apply black formatting to test_mail_processor_imap.py
(
3c8bb4b)
v0.4.1 (2026-03-28)
Bug Fixes
- Use UID-based IMAP commands and batch STORE operations to fix T-Online BYE errors
(
454452a)
[Unreleased]
Security
- Suppress bandit B104 false positive for
HOST = "0.0.0.0"inconfig.py; binding to all interfaces is intentional for containerised deployments.
Fixed
-
CI: add
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=trueenv toci.ymlto fix Node.js 20 deprecation warnings foractions/checkoutandactions/github-script. -
CI: fix Codecov upload step — change invalid
file:input tofiles:forcodecov/codecov-action@v5. -
Frontend: replace
<img>with<Image />fromnext/imageinProviderWizard.tsxto fixno-img-elementESLint errors. -
Fix timezone display in Mailbox Activity / Admin Logs pages: timestamps from the server were parsed as local time when no timezone indicator was present, causing relative times ("1h ago") and absolute dates to be shifted by the client's UTC offset.
-
Worker tasks: use a fresh DB session for
send_user_notificationcalls and move notifications afterdb.commit()to prevent the post-rollbackgreenlet_spawnSQLAlchemy error. -
Worker tasks: ensure
last_check_atand error status are always committed before notifications, fixing accounts being endlessly re-queued after IMAP auth failures. -
Style: apply black formatting to
backend/tests/unit/test_mail_processor_imap.pyto fix CI black check failure.
v0.4.0 (2026-03-28)
Features
- dashboard: Replace per-run table with per-account Mailbox Status view
(
6afd7c8)
[Unreleased]
Changed
- Dashboard "Recent Processing Runs" table replaced with a per-account Mailbox Status view: each account now shows its last-check status (OK / Error / Pending), relative last-check time, any error message, and lifetime processed/failed counters. The noisy per-run table is gone; full activity history remains available on the Logs page.
- Stats cards updated: "Emails Forwarded Today" → "Emails Processed" (all-time total from account records); "Errors" → "Accounts with Errors" (count of accounts currently showing an error).
- IMAP: switched to UID-based commands (
UID SEARCH,UID FETCH,UID STORE) instead of volatile sequence-number-based commands. Sequence numbers shift whenever other messages are expunged, causing the wrong messages to be targeted and triggering "Too many invalid IMAP commands" errors on strict servers like T-Online. UIDs remain stable for the lifetime of a mailbox.
Fixed
- Provider logos now appear on the Mail Accounts page:
provider_nameis correctly saved when creating accounts via the provider wizard and propagated through backend/frontend schemas. - Fetch-emails button now shows a text label ("Fetch"), a descriptive tooltip, a "Fetching…" loading state, and a brief green "Queued!" confirmation after the action completes.
- IMAP: eliminated redundant per-message
STORE +FLAGS \Seen— RFC822 FETCH implicitly marks a message as\Seenon IMAP servers, making the extra round-trip unnecessary. This reduces the total command count by N per polling cycle. - IMAP: batch
STORE +FLAGS \Deleted— whendelete_after_forwardis enabled, all successfully fetched UIDs are now marked for deletion in a singleUID STORE uid1,uid2,…command instead of one command per message. - IMAP: batch re-marking of stale UIDs — UIDs already tracked in the database that still appear as UNSEEN on the server (e.g. because a previous STORE failed) are now re-marked
\Seenwith a single batch command instead of one STORE per message. - IMAP: graceful BYE handling — the IMAP client is now stored in a variable so the
finallyblock always attempts a cleanlogout(). If the server has already sentBYEand closed the connection the logout failure is swallowed silently, preventing it from masking the original error.
v0.3.2 (2026-03-28)
Bug Fixes
- Resolve 21 mypy type errors across backend modules
(
0698eca)
v0.3.1 (2026-03-28)
Bug Fixes
- Test connection always showed success; add per-account test endpoint
(
441e8b5) - Fixed 21 mypy type errors across
notification_service.py,mail_processor.py,auth.py,tasks.py,providers.py,mail_accounts.py, andmain.py
v0.3.0 (2026-03-28)
Features
- Mailbox-centric activity view, reduce log noise from empty polling cycles
(
dd4532f)
v0.2.2 (2026-03-28)
Bug Fixes
- Test Connection always showed "success": frontend never checked the
successfield returned by the backend — any HTTP 200 response (including{success: false}) was displayed as "Connection successful!". Now the actualsuccessvalue is checked and authentication failures are shown as errors with the server's message. - Test Connection in edit mode required password re-entry: added backend endpoint
POST /mail-accounts/{account_id}/testthat decrypts and uses the stored credentials so existing accounts can be tested without re-typing the password.
Bug Fixes
- Resolve semantic-release CHANGELOG.md not updating properly
(
d3e0ca4)
Chores
- deps: Bump cryptography
(
2fd018a)
[Unreleased]
Added
- Pull Now: Added a "Pull Now" button (⟳) to each mail account card on the Accounts page. Clicking it immediately queues a Celery
process_mail_accounttask for that account via the newPOST /mail-accounts/{id}/pull-nowbackend endpoint. The button shows a spinner while the request is in flight and is disabled for inactive accounts. - Provider logos: Provider logos (Gmail, GMX, WEB.DE, Outlook, Yahoo, AOL, T-Online, IONOS, Freenet, Posteo, iCloud, Proton Mail) are displayed as a full-width banner at the top of each account card. Using
next/imagewithfill+object-containensures every logo – from square icons to very wide wordmarks (up to 6:1 aspect ratio) – renders correctly without distortion. - Proton Mail: Added Proton Mail as a provider preset (backend + ProviderWizard). Supports IMAP and POP3 via Proton Mail Bridge (default ports 127.0.0.1:1143 / 127.0.0.1:1144). Domains:
proton.me,protonmail.com,protonmail.ch,pm.me.
Changed
- Mailbox Activity view: The user-facing "Logs" page has been redesigned to a mailbox-centric layout (renamed "Mailbox Activity"). Each mail account is shown as a card with its last check status and error (if any). Only runs that actually fetched emails are shown in the pull history, eliminating noise from empty polling cycles. This mirrors Gmail's external POP pull UI.
- Processing runs filter: Added
has_emailsquery parameter toGET /processing-runsandGET /mail-accounts/{id}/processing-runs. Whenhas_emails=true, only runs withemails_fetched > 0are returned, allowing clients to suppress empty polling noise.
Fixed
- Processing log durations: Runs that were killed by SIGKILL or failed before updating their
own status (e.g. missing SMTP credentials) now always record a correct
completed_atandduration_seconds. The error handler no longer accesses an expired SQLAlchemy ORM attribute (run.started_at) after a session rollback, which previously caused the handler to crash and left runs stuck in therunningstate indefinitely. - Celery datetime crash: Fixed
TypeError: can't subtract offset-naive and offset-aware datetimesinprocess_all_enabled_accountsby wrappingaccount.last_check_atwith the existing_as_utc()helper before comparing againstdatetime.now(timezone.utc). This crash silently prevented every mail account from being processed on every scheduled run. - Per-account polling interval: Changed the Celery beat schedule for
process_all_enabled_accountsfrom every 5 minutes (*/5) to every minute (*). The per-accountcheck_interval_minutesfield already gates whether an account actually gets processed, so accounts configured with a 1-minute interval are now polled as expected instead of being limited to 5-minute effective intervals. - Processing log early-exit path: When a mail account has no delivery method configured
(SMTP credentials missing and Gmail API not set up), the processing run now correctly sets
completed_at,duration_seconds, andaccount.last_check_at, preventing the account from being re-queued on every scheduler tick and generating a flood of failed runs. - Stale-run detection moved to scheduler:
process_all_enabled_accounts(runs every 5 min) now marks orphanedrunningruns asfailedimmediately. Previously this only happened in the dailycleanup_old_logstask, meaning stale runs could show huge durations (hours/days). - Duration display rounding bug:
formatDurationin the frontend Processing Logs page now usesMath.floorinstead ofMath.roundfor the seconds component, eliminating the "60s" artefact that appeared for durations very close to a whole minute boundary.
Changed
- Decoupled Gmail permissions from Google Sign-In: The "Sign in with Google" OAuth flow now only requests basic profile scopes (
openid,email,profile) instead of also requesting Gmail API scopes (gmail.insert,gmail.labels,gmail.readonly). Users can grant Gmail access separately via the "Connect Gmail" button in Settings. This results in a simpler, permission-free login experience.
[0.2.1] - 2026-03-27
Fixed
SyntaxWarningat startup: Fixed invalid escape sequence\Sin a docstring inmail_processor.py(changed to\\S). In Python 3.12+ this emits aSyntaxWarningand will become aSyntaxErrorin a future Python version.- Processing runs stuck in "running" state: Fixed three related bugs in
tasks.pythat causedProcessingRunrecords to remain in therunningstate indefinitely:- The exception handler now calls
await db.rollback()before attempting to write thefailedstatus, ensuring the SQLAlchemy session is in a clean state even when the original exception occurred during a DB flush/commit. - The error-handler
await db.commit()is now wrapped in its owntry/exceptso a commit failure inside the handler no longer propagates silently and leaves the run asrunning. account.last_check_atis now updated in the error path, throttling re-dispatch byprocess_all_enabled_accountsand preventing a cascade of newrunningruns on every scheduler tick.
- The exception handler now calls
- Stale "running" run cleanup:
cleanup_old_logsnow marks anyProcessingRunthat has been in therunningstate for longer than 35 minutes (Celery hard time-limit is 30 min) asfailedwith an explanatory message. This recovers runs left behind by OOM kills, container restarts, or other SIGKILL events.
[0.2.0] - 2026-03-27
Added
- Automatic release numbers (
pyproject.toml): Addedversion_toml = ["pyproject.toml:project.version"]to[tool.semantic_release]so thatpython-semantic-releasenow writes the computed version back into theproject.versionfield ofpyproject.tomlon every release. The field is initialised to0.0.0and will be bumped automatically from that point forward. - Release badge (
README.md): Added a dynamic "GitHub Release" shield that always shows the latest published release tag. - Releases section (
README.md): Added a "Releases" section explaining the Semantic Versioning / Conventional Commits workflow and the version-bump rules.
Fixed
- CI
update-k8s-manifestjob: Fixed image tag computation andyqupdate patterns to targetregistry.cklnet.com(private registry) instead ofghcr.io. The k8s manifest uses private registry image references, so the previous GHCR-based patterns never matched and no tag updates were applied. - CI
update-k8s-manifestjob: Enhanced the PAT validation step to verify the token actually has read access to thek8s-cluster-staterepository (via a GitHub API probe) before attempting checkout, preventing a 403 "Write access to repository not granted" failure when the PAT exists but lacks the necessary repository access.
[0.1.2] - 2026-03-27
Fixed
- CI
update-k8s-manifestjob: Added aCheck if GH_PAT is configuredstep that emits a warning and skips the GitOps steps when theGH_PATsecret is absent or empty, preventing a 403 "Write access to repository not granted" failure that blocked the pipeline when the secret was not set.
[0.1.1] - 2026-03-27
Fixed
- CI
update-k8s-manifestjob: Fixed checkout ofk8s-cluster-staterepo by addingref: mainto theactions/checkoutstep, preventing a "Not Found" 404 error caused by the action's API call to determine the default branch. Also corrected the image tag format frommain-<sha>tosha-<sha>to match the tags actually generated bydocker/metadata-action@v5withtype=sha. ProgrammingErroronnotification_configs: Added Alembic migration0001that runsALTER TABLE notification_configs ADD COLUMN IF NOT EXISTSfor thenameandapprise_urlcolumns introduced by the Apprise PR. SQLAlchemy'screate_alldoes not ALTER existing tables, so existing deployments were missing these columns and crashing at runtime. The migration is idempotent (IF NOT EXISTS) so it is safe for fresh installs too.app/main.pylifespan now runsalembic upgrade headaftercreate_all./logspage 404: Created missing Next.js page atsrc/app/logs/page.tsx. The user-facing "Logs" sidebar link was pointing to/logsbut no page existed. The new page lists all processing runs with expandable per-email log details and pagination./admin/logspage 404: Created missing Next.js page atsrc/app/admin/logs/page.tsx. The admin "Activity Logs" sidebar link was pointing to/admin/logsbut no page existed. The new page shows all processing runs across all users with status filtering and pagination.- Black formatting:
backend/app/api/v1/endpoints/admin.pywas not formatted correctly; reformatted to passblack --check.
[0.1.0] - 2026-03-26
Added
- Semantic Release (
release.yml): Automated versioning and GitHub Release creation on every push tomainusingpython-semantic-release. Reads conventional-commit prefixes (feat:,fix:, etc.) to determine the next version and updatesCHANGELOG.md. pyproject.toml: Project metadata and[tool.semantic_release]configuration forpython-semantic-release.- GitOps auto-deployment (step in
ci.yml): After a successful Docker build onmain, a newupdate-k8s-manifestjob checks outchristianlouis/k8s-cluster-state(using theGH_PATsecret) and updates the backend and frontend image tags inapps/gmail-puller/preprod/gmail-puller-stack.yamlto the newmain-<sha>image, then commits and pushes. - Processing logs & reporting — users can now view the full history of polling runs and per-email delivery status:
GET /processing-runs— paginated list of all processing runs for the authenticated user's mailboxes (filterable by account and status).GET /processing-runs/{id}— details for a single run.GET /processing-runs/{id}/logs— per-email log entries (subject, sender, size, delivery status, error details) for a given run.GET /mail-accounts/{id}/processing-runs— runs scoped to a single mailbox.GET /mail-accounts/{id}/logs— all per-email log entries for a single mailbox.
- Admin log endpoints (superuser only):
GET /admin/processing-runs— all runs across every user, filterable by user ID, account ID, or status. Account and user email addresses are GDPR-pseudonymised.GET /admin/processing-logs— all per-email log entries system-wide, filterable by user, account, run, or log level. Sender (From:) headers are pseudonymised viamask_from_header(); subjects are shown as-is (user-owned content).
backend/app/core/gdpr.py— GDPR masking utilities:mask_email(),mask_name(),mask_from_header()for pseudonymising PII in admin views.- Worker now writes
ProcessingLogentries per email — subject, sender, size, delivery outcome and error detail are captured for every email processed byprocess_mail_account. /logspage — user-facing log page with a paginated processing-run table; each row expands inline to show the per-email log for that run (subject, masked sender, size, status)./admin/logspage — admin view with two tabs: Processing Runs (expandable, fetches per-email logs on demand) and Per-Email Logs (flat table with GDPR-masked sender addresses). Filterable by user ID and status/level.- Sidebar navigation — added Logs link (user) and Activity Logs link (admin) to
DashboardLayout. - Admin overview — added Activity Logs card to
/adminpage. - Dashboard — "Recent Processing Runs" table now reads from the new
/processing-runsendpoint; shows account name and a View all logs link. - Apprise alerting: New
NotificationServiceusing Apprise for multi-channel push notifications (Telegram, Slack, Discord, webhooks, and 80+ other services via a single URL scheme).send_user_notification— sends to all enabled per-user Apprise channels on processing errors or failures.send_admin_notification— sends to all enabled admin-wide channels for system events.test_notification— validates an Apprise URL by dispatching a test message.
NotificationConfigmodel: Addedname(friendly label) andapprise_url(nullable Apprise URL) columns.AdminNotificationConfigmodel: New table (admin_notification_configs) for system-wide admin alert channels withname,apprise_url,is_enabled,notify_on_errors,notify_on_system_events, anddescriptionfields.- Notifications API (
/api/v1/notifications): Full CRUD endpoints (GET/POST/PUT/DELETE) plus a/testendpoint for user notification configs. - Admin Notifications API (
/api/v1/admin/notifications): Full CRUD +/testendpoints for admin notification configs, superuser-only. - Task integration:
process_mail_accountnow callssend_user_notificationon Gmail credential revocation, per-email forwarding failures, and unhandled processing exceptions. - Configurable Gmail import labels: Users can now define which Gmail labels are applied to imported messages from the Settings page. The default setup is opinionated:
{{source_email}}(rendered to the mailbox address each message came from) plusimported, and a reset button restores those defaults instantly. - Prometheus metrics (
/metricsendpoint on the FastAPI backend, scraped every 15 s):- HTTP layer —
http_requests_total(counter, labelledmethod/endpoint/status_code) andhttp_request_duration_seconds(histogram). Path segments that are numeric IDs are normalised to{id}to avoid label-set explosion. - Mail processing —
mail_processing_runs_total(counter, bystatus:completed/partial_failure/failed),mail_processing_emails_total(counter, byoperation:fetched/forwarded/failed),mail_processing_duration_seconds(histogram),active_mail_accounts_total(gauge — set each scheduler cycle). - Gmail API —
gmail_api_requests_total(counter, byoperationandstatus),gmail_api_duration_seconds(histogram, byoperation),gmail_token_refreshes_total(counter),gmail_credentials_invalidated_total(counter). - Authentication / OAuth —
auth_logins_total(counter,method×status),auth_registrations_total(counter,method×status),oauth_callbacks_total(counter,provider×status). - Celery tasks —
celery_tasks_total(counter,task_name×status) andcelery_task_duration_seconds(histogram, bytask_name).
- HTTP layer —
- All metrics defined as module-level singletons in
backend/app/core/metrics.py(imported by HTTP middleware, task workers, GmailService, and auth endpoints). - Prometheus service added to
docker-compose.new.yml(port 9090, 30-day retention, config frommonitoring/prometheus.yml). - Grafana service added to
docker-compose.new.yml(port 3001, auto-provisioned datasource + pre-built dashboard). Default credentials:admin/admin. - Pre-built Grafana dashboard (
monitoring/grafana/dashboards/inboxconverge.json) with five sections: Mail Processing, Gmail API, Authentication & OAuth, HTTP API, and Celery Workers. Dashboard auto-refreshes every 30 s. - Admin interface: Superusers now have access to a dedicated Admin section in the sidebar with three pages:
- Admin Overview (
/admin): System-wide stats (total users, mail accounts, processing runs). - Manage Users (
/admin/users): Table of all registered users with their subscription tier, status, mail account count, and last login. Admins can edit any user's name, email, plan, active status, and promote/demote admin (superuser) privileges. Users can be deleted (with confirmation). - Manage Plans (
/admin/plans): Full CRUD for subscription plans—create, edit, and delete plans with fields for tier, name, pricing, max mailboxes, max emails/day, check interval, and support level.
- Admin Overview (
- Auto-promotion of admin email: When the user whose email matches the
ADMIN_EMAILenvironment variable logs in or registers (via email/password or Google OAuth), they are automatically promoted to superuser. Default value ischristian@inboxconverge.com(configurable via theADMIN_EMAILenv var). is_superuserfield in API responses:GET /users/meand all admin user endpoints now includeis_superuserso the frontend can conditionally show admin UI.- New admin API endpoints (all require superuser role):
GET /admin/users– List all users with mail account counts.GET /admin/users/{id}– Get a single user's details.PUT /admin/users/{id}– Update user details, plan, active status, and superuser flag.DELETE /admin/users/{id}– Delete a user.GET /admin/plans– List all subscription plans (including zero-price / inactive).POST /admin/plans– Create a new subscription plan.PUT /admin/plans/{id}– Update a subscription plan.DELETE /admin/plans/{id}– Delete a subscription plan.
- Admin badge in top bar: Admin users see a purple shield icon and an "Admin" badge next to their email in the top navigation bar.
DEFAULT_USER_TIERenv var: Controls the subscription tier assigned to every new user on registration. Defaults tofree. Set toenterprise(or any other tier) for B2B / Google Workspace installations where all employees should start on a zero-rate plan.ALLOWED_DOMAINSenv var: Comma-separated list of permitted email domains (e.g.company.com,subsidiary.com). When set, only addresses from those domains may register or log in. Superusers always bypass this check. Empty (default) = no restriction (normal B2C mode).- Dynamic pricing section on landing page: The home page now fetches
GET /subscriptions/plansand renders a pricing section only when paid plans exist. In enterprise / all-zero-rate deployments the pricing section is silently hidden — the page just shows features and a "Get started free" CTA. - B2C copy and branding: App renamed to InboxConverge throughout. Landing page hero, feature cards, how-it-works, and footer rewritten in a personal, consumer-friendly tone. Pricing updated to €0.99 / €1.99 / €2.99 per month for Good / Better / Best plans.
- Impressum & Datenschutz pages: Added
/impressum(legal notice per § 5 TMG) and/datenschutz(comprehensive privacy policy covering GDPR/DSGVO, CCPA, LGPD, and other international regulations) as public pages. Footer links to both pages were added to the dashboard layout and the login page. - Gmail Debug Email: New "Send Debug Email" button in the Gmail API settings section. When clicked, it injects a test email into the user's Gmail inbox via the Gmail API. The message includes the current date in the subject line and is automatically labelled with
testandimported. Useful for verifying end-to-end Gmail API delivery without requiring a full mail-account polling cycle. - Unified Google OAuth flow: Google Sign-In now requests all Gmail API scopes (
gmail.insert,gmail.labels,gmail.readonly) in the same consent screen, so users no longer need a separate "Connect Gmail" step after signing in with Google. Gmail credentials are stored automatically on successful sign-in. - Architecture Decision Records ADR-003 through ADR-010: Added eight new ADRs covering FastAPI web framework (ADR-003), PostgreSQL database (ADR-004), Celery task retry strategy (ADR-005), key management in production (ADR-006), JWT authentication (ADR-007), Next.js frontend (ADR-008), Gmail API email delivery (ADR-009), and hybrid configuration model (ADR-010).
- Account enable/disable toggle:
PATCH /mail-accounts/{id}/togglebackend endpoint and a Power-icon toggle button on each account card in the UI. Disabled accounts are visually dimmed. Re-enabling an account that was in ERROR state resets its status to ACTIVE so the scheduler picks it up again. - Message deduplication tracking (
DownloadedMessageIdtable): Both POP3 and IMAP fetch paths now track downloaded message UIDs so the same message is never delivered twice, even whendelete_after_forward=False. - Gmail API "one-click" OAuth grant flow: New
GET /providers/gmail/authorize-urlandPOST /providers/gmail/callbackendpoints with offline access and long-lived refresh tokens. - Gmail token auto-refresh and persistence:
GmailServicenow records whether thegoogle-authlibrary refreshed the access token during a Celery run and persists any new access token back toGmailCredential, eliminating unnecessary extra refresh calls. - Per-user SMTP relay configuration (
UserSmtpConfigtable): NewGET/PUT/DELETE /users/smtp-configendpoints let each user store their own SMTP relay. The Celery task checks for per-user SMTP first; falls back to the globalAppSettingSMTP config if none is set. - Settings page — Gmail & SMTP sections: Shows a "Gmail API Delivery" card with connection status and connect/re-authorise/disconnect buttons, plus an "SMTP Fallback" card for per-user SMTP relay credentials.
- Celery scheduling fix:
process_all_enabled_accountsnow polls allis_enabled = Trueaccounts regardless of status, so transient errors are retried automatically. - Backend URL logged at startup: The Next.js server now logs the resolved
BACKEND_URLviasrc/instrumentation.tswhen the server starts, making it easy to diagnoseECONNREFUSEDproxy errors. - Dual-registry Docker deployment: CI now builds separate backend and frontend images and pushes to both GHCR (
ghcr.io) and private registry (registry.cklnet.com) using a matrix strategy. - Database-backed configuration:
AppSettingmodel andConfigServicefor hybrid config (DB-first, env-var fallback). Admin API endpoints for managing settings (GET/PUT/DELETE /api/v1/settings). Default settings seeded into database on first startup (SMTP, processing, Gmail API, notifications). - Unit tests for
ConfigService(24 tests covering resolution order, CRUD, SMTP helper, defaults). - Security validation for
SECRET_KEYandENCRYPTION_KEYon startup. - CSRF protection middleware and security headers middleware (X-Frame-Options, CSP, HSTS).
- Rate limiting per user/tier.
- Comprehensive test infrastructure setup, CI/CD pipeline, and Dependabot configuration for automated dependency updates.
Changed
- Project renamed to InboxConverge: All user-visible strings, Docker container names, database defaults, Docker image paths, monitoring job names, Grafana dashboard titles, and documentation updated from the legacy names (
POP3 to Gmail Forwarder,InboxRescue,gmail-puller,pop3_forwarder, etc.) to InboxConverge /inboxconverge. - Domain updated to
inboxconverge.com: All contact and administrative email addresses now default to@inboxconverge.com(e.g.christian@inboxconverge.com). - Configurable contact details: Two new environment variables make contact information overridable at deployment time:
CONTACT_EMAIL(default:christian@inboxconverge.com) — used by the frontend legal pages (Impressum, Datenschutz) and surfaced in the backendSettings.APP_URL(default:https://inboxconverge.com) — the canonical public URL of the deployment.NEXT_PUBLIC_APP_NAME(default:InboxConverge) — the application name shown in frontend legal-page titles; readable by Next.js server components at runtime.
- Legacy script renamed:
pop3_forwarder.py→inboxconverge.py; rootDockerfileandMakefileupdated accordingly. - Grafana dashboard file renamed:
monitoring/grafana/dashboards/inboxrescue.json→inboxconverge.json. - Note on encryption salt: The internal PBKDF2 salt
b"pop3_forwarder_0"inbackend/app/core/security.pyis intentionally not renamed — changing it would invalidate all existing encrypted credentials stored in the database. NotificationConfigBaseschema:namefield now has a default of"My Notification"(previously required);apprise_urlis optional;config(channel-specific JSON) is optional with a default of{}.- Configuration system now supports database-backed settings in addition to environment variables.
- Celery tasks (
tasks.py) useConfigServicefor SMTP config instead of rawos.getenv()calls. - Bumped Docker Python base image from
3.11-slimto3.14-slimand CI Python version from 3.11 to 3.14. - Bumped CI Node.js version from 18 to 20.
- Bumped GitHub Actions:
actions/setup-pythonv5 → v6,actions/setup-nodev4 → v6,docker/setup-buildx-actionv3 → v4,codecov/codecov-actionv3 → v5. - Bumped backend dependencies: pydantic 2.5.3 → 2.12.5, pydantic-settings 2.1.0 → 2.9.1, asyncpg 0.29.0 → 0.31.0, stripe 7.11.0 → 14.4.1, celery 5.3.6 → 5.6.2, redis 5.0.1 → 7.3.0.
- Bumped frontend dependencies: react 19.2.3 → 19.2.4, axios ^1.13.5 → ^1.13.6, eslint-config-next 16.1.6 → 16.2.1.
Fixed
- ESLint parse error in
DashboardLayout.tsx: Missing comma afterBellin thelucide-reactnamed import caused a TypeScript parse error (',' expectedat line 19). Added the missing comma. /processing-runsendpoint 404s: Routes inlogs.pyhad a redundant/processing-runspath segment (the router was already mounted at/processing-runsinapi.py). All three user-facing log endpoints now return correct results.NotificationConfigCreateschema test failure:NotificationConfigBase.namewas a required field (...) but the unit test and the database column both use a default of"My Notification". Changed the Pydantic field todefault="My Notification"to match the DB default and allow callers to omit the field.- Test email sender name corrected from "Christian Loris" to "Christian Krakau-Louis".
- Mailbox limit always hit at 1: The
subscription_planstable was never seeded, so the limit check fell back to the env-var default. Fixed by seeding four defaultSubscriptionPlanrows at startup (Free, Good, Better, Best) and rewriting the limit check to look up the user's active plan from the DB first. - Zero-price plans hidden from public marketing:
GET /subscriptions/plansnow only returns plans withprice_monthly > 0. - TypeError: can't subtract offset-naive and offset-aware datetimes in
process_mail_accounttask when computingduration_seconds. After a database refresh,started_atmay be returned as a naive datetime; it is now normalized to UTC before subtraction. - Admin user not seeing admin dashboard: Added startup auto-promotion in
main.pylifespan handler so users matchingADMIN_EMAILare promoted to superuser on every application start. - Blank page on direct navigation to
/admin,/admin/users,/admin/plans: Moved superuser guard inside the<AuthGuard>/<DashboardLayout>tree so authentication always runs first. - Mailbox edit form: Multiple fixes including username field pre-population, silent credential overwrite prevention, and all connection fields made editable.
- Wizard grey screen:
bg-opacity-75replaced with/75opacity modifier syntax for Tailwind CSS v4 compatibility. - Mail account creation always failing: Added missing
email_addressandforward_torequired fields to theAddMailAccountModalform. - Settings page: Implemented the Settings page (was a placeholder showing "coming soon").
sqlalchemy.exc.DBAPIError: Fixed timezone-naive vs timezone-aware datetime mismatch by changing allDateTimecolumns toDateTime(timezone=True).ProgrammingError(cached statement plan is invalid): Disabled asyncpg prepared statement cache (prepared_statement_cache_size=0) to fix DDL-at-startup scenarios.UndefinedTableErroron first boot: Lifespan startup event now callsBase.metadata.create_all()before attempting to seed default settings.- Frontend API calls hardcoded to
localhost:8000: ReplacedNEXT_PUBLIC_API_URLmechanism with a Next.js Route Handler proxy at/api/v1/[...path]that readsBACKEND_URLat server startup. - Infinite spinning wheel on home page:
authStoreno longer initialisesisLoadingastrueunconditionally — it is nowfalsewhen no access token exists inlocalStorage. useSearchParams()Suspense boundary: WrappeduseSearchParams()in aSuspenseboundary infrontend/src/app/auth/callback/page.tsx.- Various TypeScript build errors in accounts page, auth callback, and dashboard pages.
- Node.js base image: Upgraded from
node:18-alpinetonode:20-alpineto satisfy Next.js requirements. - Build attestation step removed:
actions/attest-build-provenanceis not available for private user-owned repositories; removed it to fix CI. - ESLint downgraded: Downgraded ESLint from
^10to^9to fixTypeError: contextOrFilename.getFilename is not a function. - SQLAlchemy upgraded: Upgraded from
2.0.25to2.0.48to fixAssertionErroron Python 3.14. - JWT
subclaim now encoded as string per JWT spec. - Replaced deprecated
datetime.utcnow()withdatetime.now(timezone.utc)throughout backend. - Replaced deprecated FastAPI
@app.on_event()handlers with modernlifespancontext manager. - Replaced deprecated Pydantic
class Configwithmodel_config = ConfigDict(...)in all schemas. - Open redirect vulnerability in OAuth
redirect_urifixed.
Removed
- Removed CodeQL analysis from CI pipeline (was blocking builds).
Security
- Upgraded
python-josefrom 3.3.0 to 3.5.0 to fix CVE: algorithm confusion vulnerability with OpenSSH ECDSA keys. - Security headers added to all API responses.
- CSRF protection middleware added.
- Input validation improved for all endpoints.
- Credential handling audited and improved.
- Restricted overly permissive CORS
allow_methodsin backend.
[1.0.0] - 2026-02-01
Note
: This was a pre-rewrite baseline version. The current v0.x series begins from v0.1.0 (2026-03-26).
Added
- Multi-tenant SaaS backend with FastAPI
- JWT and OAuth2 (Google Sign-In) authentication
- Encrypted credential storage with Fernet
- Subscription management with Stripe integration
- PostgreSQL database with SQLAlchemy ORM
- Redis for caching and session management
- Celery for background task processing
- Apprise for multi-channel notifications
- Docker and docker-compose support
- Comprehensive API documentation with OpenAPI
Changed
- Upgraded from single-user script to multi-tenant platform
[0.0.1] - 2025-12-15
Note
: Legacy single-user script release (previously labelled
[0.1.0] - 2025-12-15 (Legacy Version)).
Added
- Initial release of single-user
inbox_converge.pyscript - Docker support with docker-compose
- Multiple POP3 account support
- Gmail forwarding via SMTP
- Rate limiting and throttling
- Error notifications via Postmarkapp
- Environment-based configuration
- Basic logging