Files
gh-christianlouis-inboxconv…/CHANGELOG.md
T
semantic-release a0d863d5ae 0.4.0
Automatically generated by python-semantic-release
2026-03-28 20:09:24 +00:00

34 KiB
Raw Blame History

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

Fixed

  • Provider logos now appear on the Mail Accounts page: provider_name is 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.

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, and main.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 success field returned by the backend — any HTTP 200 response (including {success: false}) was displayed as "Connection successful!". Now the actual success value 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}/test that 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_account task for that account via the new POST /mail-accounts/{id}/pull-now backend 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/image with fill + object-contain ensures 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_emails query parameter to GET /processing-runs and GET /mail-accounts/{id}/processing-runs. When has_emails=true, only runs with emails_fetched > 0 are 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_at and duration_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 the running state indefinitely.
  • Celery datetime crash: Fixed TypeError: can't subtract offset-naive and offset-aware datetimes in process_all_enabled_accounts by wrapping account.last_check_at with the existing _as_utc() helper before comparing against datetime.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_accounts from every 5 minutes (*/5) to every minute (*). The per-account check_interval_minutes field 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, and account.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 orphaned running runs as failed immediately. Previously this only happened in the daily cleanup_old_logs task, meaning stale runs could show huge durations (hours/days).
  • Duration display rounding bug: formatDuration in the frontend Processing Logs page now uses Math.floor instead of Math.round for 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

  • SyntaxWarning at startup: Fixed invalid escape sequence \S in a docstring in mail_processor.py (changed to \\S). In Python 3.12+ this emits a SyntaxWarning and will become a SyntaxError in a future Python version.
  • Processing runs stuck in "running" state: Fixed three related bugs in tasks.py that caused ProcessingRun records to remain in the running state indefinitely:
    1. The exception handler now calls await db.rollback() before attempting to write the failed status, ensuring the SQLAlchemy session is in a clean state even when the original exception occurred during a DB flush/commit.
    2. The error-handler await db.commit() is now wrapped in its own try/except so a commit failure inside the handler no longer propagates silently and leaves the run as running.
    3. account.last_check_at is now updated in the error path, throttling re-dispatch by process_all_enabled_accounts and preventing a cascade of new running runs on every scheduler tick.
  • Stale "running" run cleanup: cleanup_old_logs now marks any ProcessingRun that has been in the running state for longer than 35 minutes (Celery hard time-limit is 30 min) as failed with 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): Added version_toml = ["pyproject.toml:project.version"] to [tool.semantic_release] so that python-semantic-release now writes the computed version back into the project.version field of pyproject.toml on every release. The field is initialised to 0.0.0 and 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-manifest job: Fixed image tag computation and yq update patterns to target registry.cklnet.com (private registry) instead of ghcr.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-manifest job: Enhanced the PAT validation step to verify the token actually has read access to the k8s-cluster-state repository (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-manifest job: Added a Check if GH_PAT is configured step that emits a warning and skips the GitOps steps when the GH_PAT secret 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-manifest job: Fixed checkout of k8s-cluster-state repo by adding ref: main to the actions/checkout step, preventing a "Not Found" 404 error caused by the action's API call to determine the default branch. Also corrected the image tag format from main-<sha> to sha-<sha> to match the tags actually generated by docker/metadata-action@v5 with type=sha.
  • ProgrammingError on notification_configs: Added Alembic migration 0001 that runs ALTER TABLE notification_configs ADD COLUMN IF NOT EXISTS for the name and apprise_url columns introduced by the Apprise PR. SQLAlchemy's create_all does 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.py lifespan now runs alembic upgrade head after create_all.
  • /logs page 404: Created missing Next.js page at src/app/logs/page.tsx. The user-facing "Logs" sidebar link was pointing to /logs but no page existed. The new page lists all processing runs with expandable per-email log details and pagination.
  • /admin/logs page 404: Created missing Next.js page at src/app/admin/logs/page.tsx. The admin "Activity Logs" sidebar link was pointing to /admin/logs but 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.py was not formatted correctly; reformatted to pass black --check.

[0.1.0] - 2026-03-26

Added

  • Semantic Release (release.yml): Automated versioning and GitHub Release creation on every push to main using python-semantic-release. Reads conventional-commit prefixes (feat:, fix:, etc.) to determine the next version and updates CHANGELOG.md.
  • pyproject.toml: Project metadata and [tool.semantic_release] configuration for python-semantic-release.
  • GitOps auto-deployment (step in ci.yml): After a successful Docker build on main, a new update-k8s-manifest job checks out christianlouis/k8s-cluster-state (using the GH_PAT secret) and updates the backend and frontend image tags in apps/gmail-puller/preprod/gmail-puller-stack.yaml to the new main-<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 via mask_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 ProcessingLog entries per email — subject, sender, size, delivery outcome and error detail are captured for every email processed by process_mail_account.
  • /logs page — 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/logs page — 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 /admin page.
  • Dashboard — "Recent Processing Runs" table now reads from the new /processing-runs endpoint; shows account name and a View all logs link.
  • Apprise alerting: New NotificationService using 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.
  • NotificationConfig model: Added name (friendly label) and apprise_url (nullable Apprise URL) columns.
  • AdminNotificationConfig model: New table (admin_notification_configs) for system-wide admin alert channels with name, apprise_url, is_enabled, notify_on_errors, notify_on_system_events, and description fields.
  • Notifications API (/api/v1/notifications): Full CRUD endpoints (GET/POST/PUT/DELETE) plus a /test endpoint for user notification configs.
  • Admin Notifications API (/api/v1/admin/notifications): Full CRUD + /test endpoints for admin notification configs, superuser-only.
  • Task integration: process_mail_account now calls send_user_notification on 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) plus imported, and a reset button restores those defaults instantly.
  • Prometheus metrics (/metrics endpoint on the FastAPI backend, scraped every 15 s):
    • HTTP layerhttp_requests_total (counter, labelled method/endpoint/status_code) and http_request_duration_seconds (histogram). Path segments that are numeric IDs are normalised to {id} to avoid label-set explosion.
    • Mail processingmail_processing_runs_total (counter, by status: completed / partial_failure / failed), mail_processing_emails_total (counter, by operation: fetched / forwarded / failed), mail_processing_duration_seconds (histogram), active_mail_accounts_total (gauge — set each scheduler cycle).
    • Gmail APIgmail_api_requests_total (counter, by operation and status), gmail_api_duration_seconds (histogram, by operation), gmail_token_refreshes_total (counter), gmail_credentials_invalidated_total (counter).
    • Authentication / OAuthauth_logins_total (counter, method × status), auth_registrations_total (counter, method × status), oauth_callbacks_total (counter, provider × status).
    • Celery taskscelery_tasks_total (counter, task_name × status) and celery_task_duration_seconds (histogram, by task_name).
  • 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 from monitoring/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.
  • Auto-promotion of admin email: When the user whose email matches the ADMIN_EMAIL environment variable logs in or registers (via email/password or Google OAuth), they are automatically promoted to superuser. Default value is christian@inboxconverge.com (configurable via the ADMIN_EMAIL env var).
  • is_superuser field in API responses: GET /users/me and all admin user endpoints now include is_superuser so 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_TIER env var: Controls the subscription tier assigned to every new user on registration. Defaults to free. Set to enterprise (or any other tier) for B2B / Google Workspace installations where all employees should start on a zero-rate plan.
  • ALLOWED_DOMAINS env 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/plans and 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 test and imported. 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}/toggle backend 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 (DownloadedMessageId table): Both POP3 and IMAP fetch paths now track downloaded message UIDs so the same message is never delivered twice, even when delete_after_forward=False.
  • Gmail API "one-click" OAuth grant flow: New GET /providers/gmail/authorize-url and POST /providers/gmail/callback endpoints with offline access and long-lived refresh tokens.
  • Gmail token auto-refresh and persistence: GmailService now records whether the google-auth library refreshed the access token during a Celery run and persists any new access token back to GmailCredential, eliminating unnecessary extra refresh calls.
  • Per-user SMTP relay configuration (UserSmtpConfig table): New GET/PUT/DELETE /users/smtp-config endpoints let each user store their own SMTP relay. The Celery task checks for per-user SMTP first; falls back to the global AppSetting SMTP 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_accounts now polls all is_enabled = True accounts regardless of status, so transient errors are retried automatically.
  • Backend URL logged at startup: The Next.js server now logs the resolved BACKEND_URL via src/instrumentation.ts when the server starts, making it easy to diagnose ECONNREFUSED proxy 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: AppSetting model and ConfigService for 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_KEY and ENCRYPTION_KEY on 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 backend Settings.
    • 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.pyinboxconverge.py; root Dockerfile and Makefile updated accordingly.
  • Grafana dashboard file renamed: monitoring/grafana/dashboards/inboxrescue.jsoninboxconverge.json.
  • Note on encryption salt: The internal PBKDF2 salt b"pop3_forwarder_0" in backend/app/core/security.py is intentionally not renamed — changing it would invalidate all existing encrypted credentials stored in the database.
  • NotificationConfigBase schema: name field now has a default of "My Notification" (previously required); apprise_url is 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) use ConfigService for SMTP config instead of raw os.getenv() calls.
  • Bumped Docker Python base image from 3.11-slim to 3.14-slim and CI Python version from 3.11 to 3.14.
  • Bumped CI Node.js version from 18 to 20.
  • Bumped GitHub Actions: actions/setup-python v5 → v6, actions/setup-node v4 → v6, docker/setup-buildx-action v3 → v4, codecov/codecov-action v3 → 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 after Bell in the lucide-react named import caused a TypeScript parse error (',' expected at line 19). Added the missing comma.
  • /processing-runs endpoint 404s: Routes in logs.py had a redundant /processing-runs path segment (the router was already mounted at /processing-runs in api.py). All three user-facing log endpoints now return correct results.
  • NotificationConfigCreate schema test failure: NotificationConfigBase.name was a required field (...) but the unit test and the database column both use a default of "My Notification". Changed the Pydantic field to default="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_plans table was never seeded, so the limit check fell back to the env-var default. Fixed by seeding four default SubscriptionPlan rows 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/plans now only returns plans with price_monthly > 0.
  • TypeError: can't subtract offset-naive and offset-aware datetimes in process_mail_account task when computing duration_seconds. After a database refresh, started_at may 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.py lifespan handler so users matching ADMIN_EMAIL are 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-75 replaced with /75 opacity modifier syntax for Tailwind CSS v4 compatibility.
  • Mail account creation always failing: Added missing email_address and forward_to required fields to the AddMailAccountModal form.
  • 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 all DateTime columns to DateTime(timezone=True).
  • ProgrammingError (cached statement plan is invalid): Disabled asyncpg prepared statement cache (prepared_statement_cache_size=0) to fix DDL-at-startup scenarios.
  • UndefinedTableError on first boot: Lifespan startup event now calls Base.metadata.create_all() before attempting to seed default settings.
  • Frontend API calls hardcoded to localhost:8000: Replaced NEXT_PUBLIC_API_URL mechanism with a Next.js Route Handler proxy at /api/v1/[...path] that reads BACKEND_URL at server startup.
  • Infinite spinning wheel on home page: authStore no longer initialises isLoading as true unconditionally — it is now false when no access token exists in localStorage.
  • useSearchParams() Suspense boundary: Wrapped useSearchParams() in a Suspense boundary in frontend/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-alpine to node:20-alpine to satisfy Next.js requirements.
  • Build attestation step removed: actions/attest-build-provenance is not available for private user-owned repositories; removed it to fix CI.
  • ESLint downgraded: Downgraded ESLint from ^10 to ^9 to fix TypeError: contextOrFilename.getFilename is not a function.
  • SQLAlchemy upgraded: Upgraded from 2.0.25 to 2.0.48 to fix AssertionError on Python 3.14.
  • JWT sub claim now encoded as string per JWT spec.
  • Replaced deprecated datetime.utcnow() with datetime.now(timezone.utc) throughout backend.
  • Replaced deprecated FastAPI @app.on_event() handlers with modern lifespan context manager.
  • Replaced deprecated Pydantic class Config with model_config = ConfigDict(...) in all schemas.
  • Open redirect vulnerability in OAuth redirect_uri fixed.

Removed

  • Removed CodeQL analysis from CI pipeline (was blocking builds).

Security

  • Upgraded python-jose from 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_methods in 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.py script
  • 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