17 KiB
17 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
- 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 pop3_forwarder.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