The backend already scopes the starred flag per mailbox via
ThreadAccess.starred_at. This commit exposes the feature in the
frontend: a useStarred hook, a toggle button in the thread action
bar, a starred badge in thread items, an important icon in the
thread view subject, and a search filter checkbox for starred
threads (is:starred / est:important).
Also fixes the undo toast in use-flag to propagate mailboxId so
that mailbox-scoped flags (unread, starred) can be properly undone.
Currently thread sharing is confusing as the read state is shared from
all thread accesses so we deprecate the `read_at` of the Message model
in favor of a `last_read_at` timestamp pointer on the thread access.
Furthermore, on the thread model, we add several date fields to store relevant
dates in which thread should be sorted according to the current view.
For shared mailboxes, knowing which user actually sent a message is
important UX information. This adds a `sender_user` field on Message,
set at send time (not draft creation), and displays "Sent by <name>"
in the sender's contact popover when viewing from a shared mailbox.
Entitlements system with pluggable backends and automatic mail-domain admin sync on login.
Provisioning API to create/update mail domains (bulk input, custom attributes)
Feature flags to enable/disable mail-domain creation and access management; UI and public config expose these flags
Personal mailboxes now enforce configurable prefix restrictions; blocked prefixes return a clear error. Domains can indicate identity synchronization status; when disabled, creating personal mailboxes is prevented and returns a specific error.
Avoid transferring heavy body content (especially base64 images) when
only metadata is needed. The frontend now explicitly requests the body
fields it needs via ?bodies=raw,html,text, and modals lazily fetch
body content only when editing a template.
Key changes:
- Replace 3 read serializers with a single ReadMessageTemplateSerializer
that dynamically includes body fields based on ?bodies= query param
- Cache parsed blob content on the model to avoid redundant JSON parsing
- Skip expensive base64 image validation on update when body is unchanged
- Refactor frontend modals to lazy-load body via retrieve on edit
Images are embedded as base64 data URLs directly in the BlockNote content,
unlike the message composer which uses blob uploads + CID references.
This approach keeps templates and signatures self-contained without
requiring an attachment system.
Furthermore, a email-safe safe html exporter has been created to
serialize blocknote content into html.
A new backend setting MAX_TEMPLATE_IMAGE_SIZE (default 2 MiB) controls
the maximum allowed image size for these composers.
Add outbox conditional folder to display messages having message recipients with None, retry or failed state.
Inform visually the user is something is wrong with a message.
Allow user to update MessageRecipient from failed to cancelled to hide ui warning.
Allow users to report a thread as a spam.
Also allow the user to mark the thread has not spam. Then in case of spam,
disable the attachment download for security purpose.
* 🩹(mime) fix inline HTML being treated as attachment
Previously, any MIME part with a Content-Disposition header (including
'inline') was treated as an attachment. This caused HTML email bodies
with 'Content-Disposition: inline' to appear as downloadable attachments
instead of being rendered as the message body.
The fix clarifies the classification logic:
- Parts with 'Content-Disposition: attachment' are always attachments
- Parts with 'Content-Disposition: inline' and text/plain or text/html
are treated as message body
- Parts with filenames but no disposition follow existing rules
* ♻️(mime) refactor parser to use Flanker's built-in methods
Replace manual disposition parsing with Flanker's is_attachment(),
is_body(), and is_inline() methods which properly implement RFC standards.
This fixes issues where:
- text/calendar parts were incorrectly treated as attachments
- inline HTML was misclassified
- Parts without disposition were not properly categorized
The new logic uses Flanker's built-in classification:
- is_attachment(): Content-Disposition is 'attachment'
- is_body(): No filename AND (text/* or message/*)
- is_inline(): Content-Disposition is 'inline'
* fix: Add missing disposition_header variable definition
* 🛡️(mime) add defensive exception handling for MIME classification
Add try/except blocks around Flanker's is_attachment(), is_body(), and
is_inline() methods to prevent malformed MIME from crashing the parser.
When classification fails, log the error and safely fall back to treating
all checks as False, allowing parsing to continue.
Also preserve actual Content-Type for attachments instead of forcing
text/plain to application/octet-stream, and infer appropriate file
extensions from MIME types when no filename is provided.
* ♻️(mime) refactor attachment handling with helper functions
Extract duplicate attachment construction logic into reusable helper
functions and add consistent filename extension inference across all
attachment types (attachment, inline, and fallback paths).
Changes:
- Add _infer_filename_from_content_type() to map MIME types to file
extensions for better unnamed attachment handling
- Add _build_attachment_dict() helper to eliminate ~45 lines of
duplicated attachment dict construction code
- Apply extension inference consistently to is_attachment, is_inline,
and fallback branches
- Update tests to expect new filename format (e.g., "unnamed.pdf")
- Clarify exception handler comment for better code documentation
* ✅(rfc5322) fix tests & lint
* 🐛(rfc5322) sanitize attachment filenames and add many more tests
* 🐛(rfc5322) improve RFC5322 parsing logic with nested parts, per JMAP spec
* ✨(rendering) render multipart messages in frontend
* fixup! ✨(rendering) render multipart messages in frontend
* 🐛(review) address PR review comments
- Fix _sanitize_filename to preserve file extension when truncating
- Fix MessageBodyItemSerializer: use required fields, remove Meta class
- Fix misleading comment about text body drive attachments
- Regenerate OpenAPI schema and frontend API client
* 🐛(types) fix null fallback
* fix lint and tests
---------
Co-authored-by: Riël Notermans <riel@zzapps.nl>
Co-authored-by: Riël Notermans <riel@mosa.cloud>
Co-authored-by: jbpenrath <jb.penrath@gmail.com>
Allow to users to display external images from their email
through a secure proxy endpoint to ensure security and privacy
and respect iframe csp policy.
Co-authored-by: =?UTF-8?q?Ri=C3=ABl=20Notermans?= <riel@mosa.cloud>
Add a flag `is_trashed` to Thread model in order to monitor when a thread is
fully trashed. It helps to exclude those kind of threads in some views with
inactive message like archive or spam for example.
By default we exclude those kind of threads from results until user is looking
for trashed messages.
Add DRIVE_APP_NAME variable that gets exposed via the config endpoint then
update frontend translation to use this variable into it.
Co-Authored-By: jbpenrath <jb.penrath@gmail.com>
Long threads (more than 20 messages) only displayed the first 20 messages
because the MessageViewSet used the default PAGE_SIZE of 20. The frontend
doesn't handle pagination for messages, so additional messages were silently
hidden from users.
This fix disables pagination for the messages endpoint since threads typically
don't have enough messages to require pagination.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds new config vars for incoming & outgoing attachment sizes, validated at draft stage
and when receiving emails.
---------
Co-authored-by: Riël Notermans <riel@mosa.cloud>
We recently display recipient delivery status tooltip when the message is not
yet delivered or in failure. To help user to understand why the message is not
delivered, we now display the delivery message if there is one.
- Updated Message API to return MessageRecipient instead of Contact
- In ThreadMessage component displays icon next to a recipient to display
delivered status (only if the message is not delivered yet)
- Added multipart upload capabilities for EML and MBOX files to the
message imports bucket.
- Introduced new API endpoints for initiating uploads, completing
uploads, and aborting uploads.
- Updated serializers and viewsets to handle file uploads efficiently.
- Improved frontend components to display upload progress and handle
file uploads seamlessly.
- Adjusted backend services to process uploaded files asynchronously,
ensuring better performance and user experience.
- Updated documentation and tests to reflect the new functionality.
Allow a user to ask to send a message then immediately archive
the related thread. For new message, it is not allowed to archive the thread.
Furthermore, the user can choose to use by default the send and archive action.
We want to be able to archive threads. So we update Thread model to know
if the thread is archived according to its messages state. Then update the
flag endpoint api to archive messages or threads. Also update the search indexer
to be able to search within archived threads. And finally update the frontend to
allow the user to archive a thread, get and search in archived threads.
- 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.
This PR adds a new build system for embeddable widgets and a first implementation of a "Feedback" popup widget.
It also refactors inbound message routes into channels, of which there are 2 for now: MTA (by default) and Widget. More to come!
Currently the endpoint to search users is nested under maildomain. So since we
add the new endpoint to manage maildomain accesses, we need more granularity
on this endpoint and its current implementation does not allow that. So we move
it into the UserViewset. This endpoint is accessible to superuser and maildomain
admins only. Superusers are allowed to search into the whole user database to
assign any user as maildomain admin while maildomain admin are only allowed to
search users with maildomain or mailbox access in their scope.
Introduce new API endpoints for managing maildomain accesses,
including read, write, and delete operations. Update translations
and UI components to support these features, enhancing the admin
interface for managing user access to maildomains.
Related issue : #327
Only allow patch request to update mailbox then customize serializer to only
update allowed fields and related resources (Contact, User accordingly)
Also update admin frontend to add action to edit a mailbox in the maildomain admin.
Related issue : #328, #327
When a message is received, if the auto labelling feature is enabled,
we try to add all relevant labels that are flagged for auto labelling to this
message.
Co-authored-by: jbpenrath <jb.penrath@gmail.com>
Improve openapi schema to pass abilities to frontend properly.
Then through the useAbility hook, hide/disable actions.
It concerns actions to send messages, write messages, share
thread access, crud labels, crud maildomain mailboxes.
There was an error in the openapi schema and we are not aware that
message.draftBody could be null so when it was the case, the frontend
throws an error when rendering the `MessageForm` component.
User and MailDomain have both `custom_attributes` json field. Those fields
aims to store extra data about those models specific to an instance. In order
to fulfill those fields with consistency and without requiring to fork the
project, we add two new settings `SCHEMA_CUSTOM_ATTRIBUTES_*`.
Those 2 settings expects a JSON Schema string defined through env vars.
Then on model save, those schema are used to validate data saved into
custom attributes.