When a user forward a message, attachments are now also forwarded. Also,
if some attachments has a cid and embdedded into the html body, this link is
preserved.
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>
This avoids a case where a very large import would overflow the
"celery" queue with reindex tasks that would block the inbound
processing tasks. Now we have good priorization.
Updated the DJANGO_ADMIN_URL setting to ensure it does not end with a '/'.
It allows to keep simple redirect logic from the nginx proxy server and avoid
issues with django admin route paths.
In admin, the user can now click on forced or default checkbox if the signature
is not active. The mutation executed will toggle the forced/default flag and
activate the entity if needed.
Explicit a little bit how mailbox signature/message template works to prevent
confusion (Linked to the mailbox not the user).
Currently it was not possible to edit mailbox access if there was only one
editor that has no meaning. Instead we just prevent to be able to demote
a mailbox admin if this is only one.
Improve responsive experience :
- On tablet still show both thread-panel and thread-view.
- On tablet & mobile open menu when drag thread item
- Fix some issues on small viewport (element overflows, etc.)
When a message is received today, in the thread message, the label displayed
was "{time} at {time}" so we fix that to display "Today at {time}" instead.
By default spam and trashed messages are excluded from thread search result.
But there was an edge case when the user was look up within trashed message as
trashed spam message were not returned that was weird. So now spam message are
included in results if user is looking for trashed messages.
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.
According to feedbacks, some user requests to
be able to mark message as read in the thread message so we add this feature.
We also took opportunity to refactor the ThreadMessage component
to improve code structure of this component that was really dense.
* 🩹(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>
- `to` query key now looks up within `to`, `cc` and `bcc` message fields
- Add a `to_exact` query key to looks up only in `to` message field
Resolve#467
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>
* (ops) implement production docker images
Github workflows:
- moved docker-publish jobs into a separate file on push main only
Backend:
- added healthcheck on backend Dockerfile + minor fixes
- bumped django to 5.1.15 to mitigate major CVE
- moved /healthz endpoint to /__heartbeat__ + added db connection
check
Frontend:
- Added runtime-prod Dockerfile target + minor fixes on the Dockerfile
- bump next to 15.5.9 to mitigate major CVE
- moved nginx config to standard nginx container configuration
template, fixed `scalingo_postfrontend` accordingly
Keycloak:
- Added production Dockerfile
- Removed scalingo_pgdump script & cron.json
socks-proxy:
- added package upgrades run into runtime Dockerfile stage
Misc:
- removed scalingo_pgdump.sh & cron.json
- fixed compose and e2e nginx configs with /healthz replacement
* (fix) coderabbit recommandations
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 ...
When user replies to all, we populate the `to` field with both `to` and `cc`
recipients that was not the expected behavior so we fix it to populate properly
`to` and `cc` fields according to the situation.
Next 16 removes the lint command so we migrate our linter configuration to
use eslint directly. It appears some rules were not applied previously but looks
relevant so we keep them and fix errors.
It was not possible to scroll in views with long data grid.
Furthermore the mailbox panel was not usable on small height
viewport so we took opportunity to also fix that.
Currently to know if an attachment already exists in configured Drive instance,
we compare file retrieved by name, size and mime type but sometimes the mime
type cannot match. As it appears that name and size are enough to compare
resources we remove the mime type check.
`@typescript-eslint/no-unused-vars` rules was only a warning but our code
contains several unused imports. To prevent that in the future we now trigger
an error for this rule to prevent to merge code with unused imports.