# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## 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`](https://github.com/christianlouis/InboxConverge/commit/d3e0ca4e33779372b53884e25cf0d5cc3478848c)) ### Chores - **deps**: Bump cryptography ([`2fd018a`](https://github.com/christianlouis/InboxConverge/commit/2fd018afa109a2fa9d087da8590323c9a9d520ed)) ## [Unreleased] ### 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-` to `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-` 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](https://github.com/caronc/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 layer** — `http_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 processing** — `mail_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 API** — `gmail_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 / 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`) 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.py` → `inboxconverge.py`; root `Dockerfile` and `Makefile` updated accordingly. - **Grafana dashboard file renamed**: `monitoring/grafana/dashboards/inboxrescue.json` → `inboxconverge.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 `/` 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