Agent-Logs-Url: https://github.com/christianlouis/InboxConverge/sessions/56fb7508-4411-4d29-8edc-9bcd6c864c45 Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
39 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
- 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. - 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. - 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.- ESLint parse error in
DashboardLayout.tsx: Missing comma afterBellin thelucide-reactnamed import caused a TypeScript parse error (',' expectedat line 19). Added the missing comma. - Black formatting:
backend/app/api/v1/endpoints/admin.pywas not formatted correctly; reformatted to passblack --check. /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:GET /processing-runs(was:GET /processing-runs/processing-runs)GET /processing-runs/{id}(was:GET /processing-runs/processing-runs/{id})GET /processing-runs/{id}/logs(was:GET /processing-runs/processing-runs/{id}/logs)
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.
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.
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.
Added
- 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.
Added
- 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.
Changed
-
NotificationConfigBaseschema:nameis now a required field;apprise_urlis an optional field;config(channel-specific JSON) is now optional with a default of{}(previously required). Existing clients must be updated to supplyname. -
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 (was "InboxConverge"). 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.
Fixed
- 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 ofTIER_FREE_MAX_ACCOUNTS=1for every user regardless of their tier. Fixed by:- Seeding four default
SubscriptionPlanrows at startup — Free, Good (€0.99), Better (€1.99), Best (€2.99). - Rewriting the limit check to look up the user's active plan from the DB first, falling back to env-var config only when no plan row exists.
- Bypassing the limit entirely for superusers (admins can always add mailboxes).
- Adding plan limit fields (
max_mail_accounts,max_emails_per_day,check_interval_minutes) toGET /subscriptions/current.
- Seeding four default
- Zero-price plans hidden from public marketing:
GET /subscriptions/plansnow only returns plans withprice_monthly > 0. Zero-rate plans (Free tier, custom enterprise plans) are still managed by admins but never shown in the public pricing UI.
Fixed
- Fixed
TypeError: can't subtract offset-naive and offset-aware datetimesinprocess_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 — on every application start, if the user matchingADMIN_EMAILexists in the database but does not yet haveis_superuser=True, they are promoted immediately. This fixes accounts created before the auto-promotion-on-login code was deployed (e.g.christian@inboxconverge.comwas logged in but saw no admin section). - Blank page on direct navigation to
/admin,/admin/users,/admin/plans: All three admin pages hadif (!user?.is_superuser) return nullbefore the<AuthGuard>was ever rendered. On a direct page load or refresh the Zustand store initialises withuser = null, so the guard fired immediately and returned an empty render —AuthGuardwas never mounted, itscheckAutheffect never ran, and the user data was never fetched. Fixed by removing the early return and moving the superuser guard inside the<AuthGuard>/<DashboardLayout>tree, so authentication always runs first.
Security
- Upgraded
python-josefrom 3.3.0 to 3.5.0 to fix CVE: algorithm confusion vulnerability with OpenSSH ECDSA keys (affected versions < 3.4.0). - Upgraded
python-jose[cryptography]from3.3.0to3.4.0to fix an algorithm-confusion vulnerability with OpenSSH ECDSA keys (CVE affects all versions < 3.4.0).
Added
- 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 appears to be from
christian@docuelevate.org, includes the current date in the subject line, and is automatically labelled withtestandimported(labels are created on first use) and placed in the inbox. Useful for verifying end-to-end Gmail API delivery without requiring a full mail-account polling cycle. GmailService.get_or_create_label()async method: lists the user's Gmail labels and returns the matching label ID, creating the label if it does not yet exist.GmailService.inject_debug_email()async method: builds a properly formatted RFC 2822 test message and callsinject_email()with the INBOX,test, andimportedlabel IDs.POST /providers/gmail/debug-emailbackend endpoint: requires a valid Gmail credential, injects the debug email, and persists any auto-refreshed access token.gmailApi.sendDebugEmail()frontend API helper andGmailDebugEmailResponseTypeScript interface.- 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. include_granted_scopes=trueadded to both the login and Gmail authorize URLs so scope additions take effect for users who previously connected.
Changed
providers.pynow imports bothencrypt_credentialanddecrypt_credentialfromapp.core.security.gmail_service.pynow importstextwrap,MIMEText,format_datetime, anddatetime/timezonefor the debug email builder.GMAIL_API_SCOPES(providers endpoint) andGMAIL_SCOPES(GmailService) now includegmail.readonly, required forusers().getProfile()access verification (fixes 403 insufficientPermissions errors).- Google Sign-In authorize URL (
GET /auth/google/authorize-url) now requests all six scopes withaccess_type=offline,prompt=consent, andinclude_granted_scopes=trueso a refresh token is always issued. - Gmail "Connect Gmail" button in Settings now redirects to
/auth/callback?state=gmail_connectinstead of the dedicated/auth/gmail-callbackpage, reducing the number of redirect URIs that must be registered in Google Cloud Console to one ({origin}/auth/callback). auth_service.pyget_google_user_infonow returnsaccess_token,refresh_token,expires_in, andscopealongside user info so the login endpoint can persist Gmail credentials in the same request.
Fixed
-
Fixed mailbox edit form: username field was marked
requiredbut was never pre-populated (the backend intentionally excludes credentials from responses), making it impossible to save edits without re-entering the username. The backend now returnsusernameinMailAccountResponseso the edit form can pre-populate it. All connection fields (protocol, host, port, use_ssl, username) are now fully editable in edit mode. The Auto-Detect button is also shown in edit mode to re-detect server settings after a protocol change. -
Fixed mailbox edit form silently overwriting stored credentials with an empty string: when the password field was left blank during an edit the frontend sent
password: "", which the backend encrypted and stored, locking the user out. The frontend now only includespasswordin the update payload when it is non-empty, and the backend additionally guards against empty-string passwords. -
Fixed mailbox edit form sending all
MailAccountCreatefields (including immutable ones likeusername,host,port) on update requests. The submit handler now builds aMailAccountUpdatepayload containing all editable fields;MailAccountUpdatenow covers every field inMailAccountBase(protocol, host, port, use_ssl, use_tls, username, email_address, and the previously supported subset). -
Fixed
Exception terminating connectionerror logged by Celery workers after every task run. The error was caused byasyncio.run()closing the event loop while the asyncpg connection pool still held open idle connections. The fix callsawait engine.dispose()inside the task's_run()coroutine (within the same event loop) so all pooled connections are closed cleanly before the loop is torn down. -
Gmail API
verify_access()returning 403 for tokens that lacked a read-capable scope: addedgmail.readonlyto all scope lists. -
Fixed three ESLint errors that caused CI to fail: removed unused
_setUserstore binding and unuseduseAuthStoreimport fromlogin/page.tsx; replaced unused_errcatch binding with a barecatch {}inlogin/page.tsx; removed auseEffectinsettings/page.tsxthat calledsetProfileFormsynchronously (flagged byreact-hooks/set-state-in-effect) — the effect was redundant becauseuseStatealready initialises the form from the auth store'suserobject, which is the same value passed asinitialDatatouseQuery. -
Fixed wizard to create new mail accounts showing a big grey screen:
bg-opacity-75was removed in Tailwind CSS v4; replaced with the/75opacity modifier syntax (bg-gray-500/75) inAddMailAccountModalandDashboardLayoutmobile overlay. Restructured the modal from the deprecatedinline-block align-bottomcentering trick to a proper flexbox layout withrelative z-10on the modal content. -
Fixed mail account creation always failing with a backend validation error:
email_addressandforward_toare required fields in the backend schema but were missing from theAddMailAccountModalform. Added both fields to the form —email_addressis auto-synced from the username input, andforward_to(destination Gmail address) is a new explicit field pre-populated from the logged-in user's email. Also addeddelivery_methodselector anddelete_after_forwardcheckbox. -
Implemented the Settings page (was a placeholder showing "coming soon"): now includes a Profile section to update name and email via
PUT /users/me, an Account Information section showing subscription tier/status and member-since date, and a Security section. -
Fixed
sqlalchemy.exc.DBAPIErrorraised by asyncpg when inserting timezone-awaredatetime.now(timezone.utc)values into timezone-naiveDateTime(TIMESTAMP WITHOUT TIME ZONE) columns: changed allDateTimecolumn definitions indatabase_models.pytoDateTime(timezone=True)(TIMESTAMP WITH TIME ZONE) and replaced alldefault=datetime.utcnowcallable references withdefault=lambda: datetime.now(timezone.utc)for consistent, timezone-aware timestamps throughout the ORM. -
Fixed
ProgrammingError(cached statement plan is invalid) raised by the asyncpg dialect during startup: SQLAlchemy's asyncpg wrapper maintains an LRU prepared-statement cache per connection (default size 100). WhenBase.metadata.create_all()executesCREATE TYPE … AS ENUMDDL inside a transaction, PostgreSQL invalidates the cached plans for that connection. The next enum-type existence check then fails because the dialect tries to reuse the now-stale prepared statement. Fix: setprepared_statement_cache_size=0inconnect_argsoncreate_async_engineto disable the cache entirely, which is the documented SQLAlchemy recommendation for DDL-at-startup scenarios. -
Fixed
UndefinedTableErroron first boot: the lifespan startup event now callsBase.metadata.create_all()via the async engine before attempting to seed default settings, so all tables are created automatically when the database is empty (e.g., fresh PostgreSQL container with no Alembic migrations run yet). -
Fixed frontend API calls being hardcoded to
http://localhost:8000in production:NEXT_PUBLIC_API_URLis baked into the JavaScript bundle at Next.js build time, so it can never be overridden at container runtime. Replaced theNEXT_PUBLIC_API_URLmechanism with a Next.js Route Handler proxy at/api/v1/[...path]that readsprocess.env.BACKEND_URLat server startup and proxies all/api/v1/*requests to the real backend. The frontend Axios client now uses a relative base URL (/api/v1), which also eliminates the CORS issue since the browser only ever talks to the same-origin Next.js server. UpdateBACKEND_URL=http://backend:8000indocker-compose.new.yml(or your deployment env) to point the proxy at your backend. -
Fixed infinite spinning wheel on the home page:
authStoreno longer initialisesisLoadingastrueunconditionally — it is nowfalsewhen no access token exists inlocalStorage, so unauthenticated users see the landing page immediately instead of an endless spinner -
Home page now performs an auth check when a token is present in
localStorage, redirecting authenticated users to the dashboard and clearing stale tokens on failure -
Wrapped
useSearchParams()in aSuspenseboundary infrontend/src/app/auth/callback/page.tsxto fix the Next.js build error: "useSearchParams() should be wrapped in a suspense boundary at page /auth/callback" -
Fixed TypeScript build error in
frontend/src/app/accounts/page.tsx: replaced non-existentaccount.usernamewithaccount.email_address,account.last_checked_atwithaccount.last_check_at, andaccount.last_errorwithaccount.last_error_message(the backend intentionally excludesusernamefrom API responses for security) -
Fixed TypeScript error in
frontend/src/app/auth/callback/page.tsx:TokenResponsedoesn't includeuser; now fetches user viauserApi.getCurrentUser()after OAuth token exchange -
Fixed TypeScript errors in
frontend/src/app/dashboard/page.tsx: replaced non-existenterrors_countwithemails_failedonProcessingRun -
Fixed TypeScript errors in
frontend/src/components/AddMailAccountModal.tsx: removed invalidaccount.usernameaccess, added missing required fields to initial form state, and fixed autoDetect suggestions access -
Made
email_address,use_tls,forward_tooptional in theMailAccountCreateTypeScript interface to align with form usage -
Added typed suggestion fields to
autoDetectreturn type inapi.ts -
Excluded test files (
*.test.ts,*.spec.ts) from TypeScript compilation intsconfig.json -
Upgraded Node.js base image in
frontend/Dockerfilefromnode:18-alpinetonode:20-alpineto satisfy the Node.js >= 20.9.0 requirement for Next.js and fix Docker build failures -
Removed
actions/attest-build-provenancestep and associatedid-token: write/attestations: writepermissions from the CIbuildjob — this action is not available for private user-owned repositories and caused every build to fail -
Downgraded
eslintfrom^10to^9in the frontend to resolveTypeError: contextOrFilename.getFilename is not a functioncaused by ESLint 10 removing thegetFilename()API used byeslint-plugin-reactbundled ineslint-config-next -
Upgraded
sqlalchemyfrom2.0.25to2.0.48to fixAssertionError: Class ... directly inherits TypingOnly but has additional attributeson Python 3.14 (__static_attributes__,__firstlineno__)
Added
- 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)
userApi.updateProfile()method infrontend/src/lib/api.tsfor updating user profile viaPUT /users/me- 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. is_enabledcheckbox in edit modal: The Add/Edit mail account form now includes an "Enabled" checkbox so the flag can be set when creating or editing an account.- 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.- IMAP: messages are marked
\Seenafter fetching so they don't appear in futureUNSEENsearches. DB UIDs provide a secondary guard. - POP3: UIDL-based deduplication; messages are skipped if their UID is already in the DB.
- Old UID records are pruned by
cleanup_old_logsafterdays_to_keepdays.
- IMAP: messages are marked
- Gmail API "one-click" OAuth grant flow: New
GET /providers/gmail/authorize-urlandPOST /providers/gmail/callbackendpoints. The flow requestsgmail.insert + gmail.labelsscopes withaccess_type=offlineso a long-lived refresh token is issued. A new/auth/gmail-callbackfrontend page handles the redirect from Google, exchanges the code, and redirects the user back to Settings. - Gmail token auto-refresh and persistence:
GmailServicenow records whether thegoogle-authlibrary refreshed the access token during a Celery run. If it did, the Celery task writes the new access token and expiry back toGmailCredential, eliminating an unnecessary extra refresh call on the next run. A401/403orinvalid_granterror during delivery marksGmailCredential.is_valid = Falseso the user is prompted to re-authorise. - Per-user SMTP relay configuration (
UserSmtpConfigtable): NewGET/PUT/DELETE /users/smtp-configendpoints let each user store their own SMTP relay (host, port, username, password, TLS flag). The Celery task checks for per-user SMTP first; falls back to the globalAppSettingSMTP config if none is set. - Settings page – Gmail & SMTP sections: The Settings page now shows a "Gmail API Delivery" card with connection status, "Connect Gmail" / "Re-authorise" / "Disconnect" buttons, and a token-lifetime explanation. An "SMTP Fallback" card lets users save their own SMTP relay credentials.
- Celery scheduling fix:
process_all_enabled_accountspreviously only polled accounts withstatus IN [ACTIVE, TESTING], causing ERROR-status accounts to be silently skipped forever. It now 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_URL(e.g.[proxy] BACKEND_URL = http://backend:8000) viasrc/instrumentation.tswhen the server starts, making it easy to diagnoseECONNREFUSEDproxy errors. The per-request error log now also includes the full target URL. - 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) - Gmail API delivery documentation with comparison table (Gmail API vs SMTP forwarding)
- GitHub issue templates (bug report, feature request, test needed)
- Pull request template with comprehensive checklist
docs/CODING_PATTERNS.mdwith development best practicesdocs/ERRORS.mddocumenting all error codesdocs/adr/directory with Architecture Decision RecordsMakefilewith common development tasks.pre-commit-config.yamlfor code quality enforcementCHANGELOG.mdfor version tracking- Security validation for SECRET_KEY and ENCRYPTION_KEY on startup
- CSRF protection middleware
- Security headers middleware (X-Frame-Options, CSP, HSTS)
- Rate limiting per user/tier
- Comprehensive test infrastructure setup
- CI/CD pipeline for testing and security scanning
- Dependabot configuration for automated dependency updates (pip, npm, GitHub Actions, Docker)
- Copilot instructions requiring TODO.md and CHANGELOG.md updates
- Unit tests for security middleware (SecurityHeadersMiddleware, CSRFProtectionMiddleware)
- Unit tests for JWT token lifecycle (access tokens, refresh tokens, decode, edge cases)
- Unit tests for credential encryption edge cases (empty, long, unicode, special chars)
- Unit tests for FastAPI application factory and core endpoints (root, health, OpenAPI)
- Unit tests for Pydantic schema validation (users, mail accounts, notifications, subscriptions)
- Unit tests for JWT
subclaim string encoding and token type verification - Created
frontend/src/lib/api.ts— API client module (fixes frontend compilation blocker) - Reached 57% test coverage (up from 54%)
Changed
- Configuration system now supports database-backed settings in addition to environment variables
- Celery tasks (
tasks.py) useConfigServicefor SMTP config instead of rawos.getenv()calls - README updated with hybrid configuration docs, Gmail API vs SMTP comparison, and Apprise notifications
- Architecture docs updated to reflect Gmail API service, hybrid config, and new API endpoints
- Reorganized documentation into
docs/directory - Improved error handling with specific exception types
- Updated datetime usage to timezone-aware
- Enhanced logging with structured context
- Bumped Docker Python base image from 3.11-slim to 3.14-slim
- Bumped 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.13.1, psycopg2-binary 2.9.9 → 2.9.11, asyncpg 0.29.0 → 0.31.0, stripe 7.11.0 → 14.4.1, aioimaplib 1.0.1 → 2.0.1, google-auth-httplib2 0.2.0 → 0.3.0, celery 5.3.6 → 5.6.2, redis 5.0.1 → 7.3.0, tenacity 8.2.3 → 9.1.4
- Bumped frontend dependencies: react 19.2.3 → 19.2.4, @tanstack/react-query ^5.90.20 → ^5.95.0, axios ^1.13.5 → ^1.13.6, zustand ^5.0.11 → ^5.0.12, eslint ^9 → ^10, eslint-config-next 16.1.6 → 16.2.1
- Synced
frontend/package.jsoneslint-config-nextto16.2.1to matchpackage-lock.json(resolvesnpm ciEUSAGE failure)
Removed
- Removed CodeQL analysis from CI pipeline (was blocking builds)
Fixed
- JWT
subclaim now encoded as string per JWT spec (python-jose rejects integer subjects) TokenPayloadschemasubfield type changed frominttostrfor consistency- 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 - Replaced deprecated Pydantic
.dict()with.model_dump()in mail account updates - Removed overly broad
except (GmailInjectionError, Exception)in task error handler - Bare exception handlers replaced with specific types
- Open redirect vulnerability in OAuth redirect_uri
- Default encryption keys security issue
Security
- All dependencies updated to patched versions
- Security headers added to all API responses
- Input validation improved for all endpoints
- Credential handling audited and improved
[1.0.0] - 2026-02-01
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
- Extensive documentation (README, ARCHITECTURE, SECURITY_REPORT, etc.)
Changed
- Upgraded from single-user script to multi-tenant platform
[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
Version History
- [Unreleased] - Current development (agentic coding improvements, security hardening)
- [1.0.0] - Multi-tenant SaaS platform (2026-02-01)
- [0.1.0] - Legacy single-user script (2025-12-15)
How to Update This Changelog
Categories
Use these standard categories:
- Added - New features
- Changed - Changes in existing functionality
- Deprecated - Soon-to-be removed features
- Removed - Removed features
- Fixed - Bug fixes
- Security - Vulnerability fixes
Format
## [Version] - YYYY-MM-DD
### Added
- New feature description (#issue-number)
### Fixed
- Bug fix description (#issue-number)
Workflow
- Add unreleased changes to
[Unreleased]section - When releasing, move unreleased changes to new version section
- Add version number, date, and comparison link
- Create git tag:
git tag -a v1.0.0 -m "Release v1.0.0"
Maintained by: Development Team Last Updated: 2026-03-23