36 KiB
36 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.
[Unreleased]
Fixed
- 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.
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