Inbound messages with duplicate recipient addresses are now handled gracefully, preventing duplicate recipient links and avoiding failures during import.
* 🐛(importer) add STARTTLS support and enforce encrypted IMAP connections
- Add STARTTLS negotiation for port 143 with use_ssl=True
- Create IMAPSecurityError exception for security violations
- Refuse to continue unencrypted when SSL is required
- Check server STARTTLS capability before attempting upgrade
- Fail fast to prevent credential leakage on security downgrade
Connection behavior:
- Port 993 + use_ssl=True → SSL direct
- Port 143 + use_ssl=True → STARTTLS (mandatory)
- Port 143 + use_ssl=False → unencrypted (explicit choice)
Fixes: SSL WRONG_VERSION_NUMBER error on port 143
* 🐛(importer) improve IMAP connection and authentication error handling
Backend improvements:
- Catch SSL handshake failures during IMAP4_SSL connection
- Provide clear error message suggesting port 143 with STARTTLS
- Add authentication failure logging with username context
- Wrap login() in try-except to log auth errors before propagating
Frontend improvements:
- Detect "IMAP authentication failed" message in error responses
- Display user-friendly credential error message in UI
This helps users understand connection failures and authentication
issues with clearer, actionable error messages.
* 🐛(imap) add tests and small fixes in the handshake
* 🐛(imap) wrap all connection login in a try/finally for cleanup
---------
Co-authored-by: Sylvain Zimmer <sylvain@sylvainzimmer.com>
Add a Python script to automate the release process:
- Validates semver version format
- Calculates next version automatically based on release kind
- Updates version in pyproject.toml, package.json and package-lock.json
- Updates CHANGELOG.md with release date
- Creates release branch and commits changes
Usage:
make release
or
bin/release.py --kind m --version ...
The has_object_permission method was failing with MultipleObjectsReturned
exception when a user had access to multiple mailboxes that both have
access to the same thread.
Changes:
- Filter thread accesses by EDITOR role and check user mailbox permissions
- Use filter().exists() instead of get() to avoid MultipleObjectsReturned
- Add test case to verify users can send messages when they have access
to multiple mailboxes on the same thread
- Allow to create and manage signature in admin interface
- Allow to insert signature in message editor
- Blob model must have at least a mailbox or maildomain
This commit introduces a complete email template system with support
for both HTML and text content, flexible relationships with mailboxes
and mail domains, and a RESTful API for template management.
- Load folders for GMAIL and ORANGE
- Fix read issue with Gmail IMAP import (all messages were marked as read)
- Fix IMAP encoding for Gmail import
- Refactor monolithic import_imap_messages_task into smaller, focused functions
- Add IMAPConnectionManager context manager for better resource management
- Extract helper functions with single responsibilities
This refactoring improves code maintainability, readability, and testability
while maintaining backward compatibility with existing API.
- Replace single label lookup with multiple labels filter in ThreadViewSet
- Allow filtering threads by label_slug across multiple mailboxes when user has access
- Expect empty results instead of 403 errors for inaccessible labels
- Add comprehensive tests for identical labels in different mailboxes scenarios
This change enables users with access to multiple mailboxes to filter threads
by label slug across all accessible mailboxes, while maintaining proper
permission checks and supporting mailbox-specific filtering.
- Add pre-calculation of user access roles in MailboxViewSet list/retrieve methods
- Implement get_abilities method on Mailbox model for permission checking
- Add AbilitiesModelSerializer to MailboxSerializer for consistent abilities field
- Optimize database queries by reducing N+1 access lookups
- Add comprehensive test coverage for abilities and optimized queries
This optimization significantly reduces database queries when listing
mailboxes by pre-calculating user roles instead of querying for each
mailbox individually.
- Add cascading rename functionality to Label model save method
- Update all child labels when parent label is renamed
- Clean up orphaned parent labels that are no longer referenced
- Fix hierarchical tree construction in LabelViewSet list method
- Handle orphaned child labels by treating them as root labels
- Add comprehensive test for parent label rename scenarios
This ensures that when a parent label is renamed, all its children
are automatically updated to maintain the hierarchy, and the API
correctly displays all labels even when some parents are missing.
Fix#278
Prevents 400 Bad Request error when there are simultaneous updating draft
Fixes the constraint error "Un objet Message recipient avec ces champs Message, Contact et Type existe déjà".
- Add get_abilities() method to MailDomain model for permission-based abilities
- Update MailDomainAdminSerializer to inherit from AbilitiesModelSerializer
- Add RetrieveModelMixin to MailDomainAdminViewSet for detail endpoints
- Optimize queries with JOIN and annotation for regular users
- Add prefetch_related for superusers to avoid N+1 queries
- Add comprehensive tests for abilities functionality and query optimization
- Add new test file for MailDomain model abilities
The abilities field dynamically controls user permissions based on their role
in the mail domain, with optimized database queries to maintain performance.
- Create AbilitiesModelSerializer base class that dynamically controls abilities field inclusion
- Refactor UserSerializer to inherit from AbilitiesModelSerializer
- Add exclude_abilities parameter to prevent abilities serialization in nested contexts
- Update MailboxAccessReadSerializer and MailboxAccessNestedUserSerializer to exclude abilities when nested
- Remove abilities from UserSerializer Meta fields as it's now handled dynamically
- Add condition in MailDomainAdminViewSet.get_queryset() to return all domains
when user is both superuser and staff
- Add comprehensive tests covering all user permission combinations:
* superuser + staff: can see all domains
* superuser only: cannot see all domains (empty list)
* staff only: cannot see all domains (empty list)
* staff with domain access: can only see accessible domains
- Maintain existing behavior for regular users and domain admins
This change ensures that superusers with staff privileges have full
administrative access to all mail domains through the admin API,
while maintaining proper access control for other user types.
- Add get_abilities() method to User model to determine user permissions
- Add abilities field to UserSerializer to expose permissions via API
- Implement permission logic for create_maildomains and view_maildomains
- Add comprehensive tests for User model abilities method
- Add integration tests for abilities field in users/me endpoint
- Support superuser+staff, mail domain access, and regular user scenarios
The abilities system provides granular permission control for mail domain
operations, allowing the frontend to adapt UI based on user capabilities.
Add dedicated admin interface for MailDomainAccess model to improve
domain access management in Django admin.
- Register MailDomainAccessAdmin
- Add MailDomainAccessInline for embedded editing on MailDomainAdmin
- Add ability to search contacts by multiple keywords (AND logic)
on name and email, in contacts endpoint.
- Adapt and enrich tests to cover multi-word search,
case sensitivity, and name/email combination.
- Add API tests for label ordering by slug in hierarchical structure
- Add model tests for Label ordering configuration and behavior
- Test ordering with numbers, accents, and mixed case scenarios
- Verify alphabetical ordering across multiple mailboxes
- Ensure consistent ordering behavior for hierarchical labels
- Add null=True, blank=True to Message and Thread subject fields
- Add comprehensive tests for empty/null/missing subjects
- Update migration 0018 to alter subject field constraints
- Fix#161: allow messages without subject field
- Change label sorting from name-based to slug-based ordering in list view
- Use slug ordering for initial queryset to ensure consistent sorting
- Maintain name-based sorting in create method for consistency
- Fixes issue where "État civil et cimetière" appeared at the end
The slug-based sorting provides more predictable alphabetical ordering
since slugs are normalized versions of names without accents.
Replace simple comma splitting with regex that respects quoted strings
to prevent labels like "Culture, associations, événements" from being
split incorrectly.
- Add delete() method to Label model to handle cascading deletion of child labels
- When a parent label is deleted, all child labels (e.g., "Work/Projects") are automatically deleted
- Implement proper logging for debugging deletion operations
- Add comprehensive tests for cascading deletion scenarios:
- Deleting parent label deletes all children
- Deleting child label doesn't affect parent
- Deleting label without children works normally
- Model-level deletion works independently of API
This ensures consistent deletion behavior across all interfaces (API, admin, programmatic)
and maintains referential integrity when deleting hierarchical label structures.
Add comprehensive Gmail label import functionality for mbox files with
support for both English and French Gmail labels.
Features:
- Map Gmail system labels to internal message flags (is_draft, is_sender, is_starred, etc.)
- Handle read/unread status from Gmail labels (Ouvert/Opened, Non lus/Unread)
- Create user labels for non-system Gmail labels
- Support hierarchical labels with proper parent/child relationships
- Ignore Gmail system labels that shouldn't be created as user labels
- Special handling for sent/draft messages (automatically marked as read)
Test coverage:
- Label creation and flag mapping for both languages
- UTF-8 encoded label handling
- Hierarchical label structure validation
- Read/unread status verification
- Authentication and authorization requirements
- Thread statistics updates
This enables proper import of Gmail labels when processing mbox files, maintaining the
original label structure while mapping system labels to appropriate internal flags.
Put the logic into Label model's save() method to guarantee the invariant always holds,
even when using the admin interfacee uses, the shell, bulk operations, or any code path.
- Add secondary ordering by created_at to prevent flaky test results
- When threads have the same messaged_at value, order by newest created_at first
- Ensures deterministic API responses for better test reliability
- Added LabelBadge component to render labels as colored badges in the thread list and thread view.
- Updated Thread and Label models to support label objects and color.
- Styled label badges and thread item label container for better visual integration.
- Integrated label display in thread list
Add the ability to filter threads by label slug in the thread list endpoint. This feature
enables users to view all threads associated with a specific label.
Changes:
- Add label_slug query parameter to ThreadViewSet list endpoint
- Implement label-based filtering with proper permission checks
- Add OpenAPI documentation for the new label_id parameter
- Add comprehensive test suite for label filtering
Technical details:
- Filter threads by label while ensuring user has access to the label's mailbox
- Support combining label filter with existing filters (mailbox, search, etc.)
- Add proper error handling for invalid or inaccessible labels
- Add test coverage for various filtering scenarios:
- Single label filtering
- Combined label and mailbox filtering
- Invalid label handling
- Permission checks
- Combined filters (label + unread/starred status)
Avoid duplicate threads when importing the same file multiple times.
Changes:
- Moved duplicate message check to the start of deliver_inbound_message
- Maintains proper threading relationships while preventing duplicate threads
This ensures that:
- No duplicate threads are created when importing the same file multiple times
Test coverage:
- Updated existing import tests to check thread counts
- Verified that duplicate imports don't create new threads
This commit implements a complete label management system with the following features:
- Add CRUD operations for labels with hierarchical support (using slash-based naming)
- Implement label-thread associations (add/remove threads to/from labels)
- Add comprehensive permission checks (ADMIN/EDITOR roles required for management)
- Support hierarchical label structure with automatic parent creation
- Add color customization for labels
- Implement proper slug generation for label names
- Add extensive test coverage for all label operations
Technical details:
- New LabelViewSet with list, create, update, delete operations
- Custom actions for thread management (add-threads, remove-threads)
- Hierarchical label structure with parent-child relationships
- Automatic parent label creation when creating nested labels
- Proper permission handling based on mailbox access roles
- Comprehensive test suite covering all edge cases
- OpenAPI documentation for all endpoints
Add duplicate message detection to prevent importing the same email multiple times
into the same mailbox. This improves data integrity and prevents unnecessary
duplication of messages.
Changes:
- Add MIME ID-based duplicate detection in deliver_inbound_message
- Skip duplicate messages gracefully with informative logging
- Allow same message to exist in different mailboxes
- Add comprehensive test suite for duplicate detection
Technical details:
- Uses message MIME ID as unique identifier for duplicate detection
- Scopes duplicate check to individual mailboxes
- Returns success for duplicate handling to maintain API consistency
- Adds tests for EML, MBOX, IMAP and cross-mailbox scenarios
This change ensures that:
- Users can't accidentally import the same email twice
- Each mailbox maintains a single copy of each message
- The same message can still exist in different mailboxes
- Import operations remain idempotent
Enhance support for MBOX files from various email export tools by handling
both extensionless files and text/plain MIME type. This improves compatibility
with different export formats while maintaining security through proper
validation.
Changes:
- Add support for files named "mbox" without extension
- Allow text/plain MIME type for MBOX files (common in webmail exports)
- Keep application/mbox as the primary MIME type for MBOX files
- Update validation to check both MIME types and file extensions
- Add test cases for extensionless files and text/plain MIME type
- Update error messages to be more descriptive about supported formats
The validation now handles:
- Files with .mbox extension (application/mbox or text/plain MIME type)
- Files named "mbox" without extension
- Maintains strict validation to ensure text/plain is only accepted for MBOX files
This change improves compatibility with various email export tools and webmail
services while maintaining security through proper file validation.
Problem:
When running python manage.py es_reindex --all, it was trying to save task progress to the database
This caused an error because there was no task ID (since it wasn't a real Celery task)
The error was: null value in column "task_id" of relation "django_celery_results_taskresult"
Fix:
We split the reindexing code into two parts:
_reindex_all_base: A regular Python function that does the actual reindexing
reindex_all: A Celery task that wraps the base function
Now:
-> When you run python manage.py es_reindex --all (synchronous):
- It uses _reindex_all_base directly
- Shows progress in the console like: "Progress: 50/100 threads processed (45 succeeded, 5 failed)"
- No database updates are attempted
-> When you run python manage.py es_reindex --all --async (asynchronous):
- It uses the Celery task reindex_all
- Progress is saved to the database
- You can monitor progress through Celery's task monitoring
Before: The same code was trying to work both ways, causing errors
After: We have separate code paths for synchronous and asynchronous execution
* ✨(backend) use ThreadAccess instead of direct mailbox link
Remove the direct link to a mailbox on Thread model. It is now possible to
define two types of roles for each thread (Reader and Editor) for a mailbox.
When creating a new thread, a new access to the sender's mailbox is created
with the Editor role.
It is now possible to delegate a thread to another mailbox with the Reader or
Editor role. So it is possible to forward a thread to another internal user
and avoid mail forwarding.
* ✨(backend) change MailboxAccess permission into roles
Enhance access on mailbox and thread with role everywhere!
* improve doc
* enhance tests
* lint