If a draft is the single thread message, delete it should not
trigger a request to refresh thread message list because this is
wasteful and it also display a toast error as the thread does not
exist anymore.
First when the sending reach the timeout, instead to display a toast
error
with a message that lets believe the message cannot be sent, we display
a warning message mentionning that sending takes more time than
expected.
Then, once a message is sent, we optimistically update thread cache to
hide
immediately the sent draft and show instead the message in the thread
view.
On slow machines, a race condition can occur when unselectThread is
trigger and another action is also triggered. e.g: when user mark as
unread a thread, the thread is unselect and the request to mark it as
unread is trigger but sometimes, the request is completed before the
router navigation so the observer in charge to mark visible message has
unread is trigger again and finally, the thread is not marked as
unread...
In f360570798, a
migration in 3 parts has been done. This was
a progressive migration and in order to be able
to restore data model in case of failure, we simply
deprecate some fields. Now that everything is fine in
production, we can safely remove those fields.
defusedxml was not declared into dependencies. In local
environment this one should be installed as transitive
deps but during deployment this deps was not found.
CalDAV-backed mailbox calendar actions: RSVP (Accept/Decline/Maybe), Add to calendar, calendar chooser, calendar listing, and conflict detection — actions enqueue background tasks and return task IDs; new API endpoints documented.
Follow-up to 6144ccb2: the graceful drop stopped the 500 on /send/,
but every reply to an Outlook/MAPI thread lost threading because
Python 3.14's MsgIDListHeader (now the default for In-Reply-To and
References) truncates obs-id-left ids with multiple '@' in the local
part at the first '@' on serialize. The pre-stdlib flanker composer
used to write those bytes through unchanged.
Route both headers to UnstructuredHeader through a dedicated
HeaderRegistry. The instance has to be dedicated: policy.clone()
shares header_factory by reference with policy.SMTP and policy.default,
so mutating in place would silently change parsing process-wide. The
msg-id regex is loosened to allow multiple '@' now that the value
goes out verbatim; the whitespace ban stays (UnstructuredHeader folds
mid-id and receivers would then truncate at the fold), as does the
graceful drop from 6144ccb2 for the cases that genuinely can't ship
(whitespace, no '@', nested brackets, CR/LF injection attempts).
The PST importer's mirror regex is loosened in sync so archive imports
preserve threading on the same kind of ids. Two parametrized tests
lock the regression surface against silent narrowing — eight real-world
shapes that must round-trip on the wire and eight that must drop.
Currently, user can delete/edit an internal message while
it is within the edit timeframe defined by
`MAX_THREAD_EVENT_EDIT_DELAY`. First feedbacks raises that
this limit is not relevant for deletion.
- Improve error management for pst
We get some pst that are unparsable by pypff. To help user to understand
that the issue is coming from the PST file, we improve the exception
raised by pst task and display a custom error message according to the
error format.
- Recover Exchange X.500-only senders during PST import
Sent items from shared mailboxes — and many internal Exchange messages —
expose every PR_SENDER_*/PR_SENT_REPRESENTING_* slot as an unresolvable
X.500 DN. compose_email then rejected the EML for lack of a valid From
address and pst_tasks silently dropped the message at debug level, so
entire folders disappeared from the import without a trace.
- Prevent duplicate messages on PST re-import
PST messages without transport_headers (drafts, locally composed items)
were reconstructed with no Message-ID at all, and Exchange/O365 exports
sometimes drop the header even on received items. With no mime_id to
key on, deliver_inbound_message skipped its dedup check and inserted
the same message on every import — and even twice within a single
import when the same message appeared in multiple Outlook folders.
Allow user to copy/paste a thread link with other mailbox users.
Currently, if the user copy the current url, the link is broken once
the thread has been moved from the origin folder. It is also possible
to target a message or internal message.
* allow to render table in email
Improve email exporter to support table elements. We do not add
blocknote tool to add explicitly table but we allow user to
copy/paste it and render it properly.
* upgrade to blocknote 0.49.0
Remove a bug that prevent to use backspace in an empty block.
https://github.com/TypeCellOS/BlockNote/pull/2610
This allows to use S3-compatible object storage to offload blobs, making Postgres much lighter. We design for storing ~1B emails on a single instance.
We also take this opportunity to do model changes on blobs & attachments.
Migration 0027 is one-way, no going back after this one so check your backups.
Latest fixes in the stdlib make it a more solid alternative for strict composition than Flanker. We keep Flanker for now for lenient inbound parsing. We add stronger tests and fuzzing to validate we didn't regress.
As a follow-up of the mention feature, we build upon ThreadEvent & UserEvent
models a feature to assign users to a thread.
We allow to filter mailboxe's inbox through assignation state (assigned to me, unassigned).
The thread share modal has been forked from ui-kit to be able to list users of each
mailbox and add a cta to assign them to the thread. A section above shows assigned users.
Currently, only mailbox editors can remove thread access to their
mailbox.
Actually, thread access deletion must be symmetric with creation rights.
So
any user with thread management ability should be able to delete a
thread
access.
The `ATTACHMENT_SEPARATORS` only contains a english separator and as
this
string is displayed within other mail client, for non-english users it
can
be weird to see an english string. That's why we add new separators and
according to the sender language, we use the localized separator that
correspond to him.
Currently when the user clicks on the refresh button, a spinner
is displayed during query is pending. Often, this query is really
fast so the user does not see the spinner and wonder if something
really happend. In order to improve ux here, we now display the
spinning icon at least for 700ms in this way, no matter the query
timing, the user will see something happend. Furthermore, we also
add a transient tooltip that display refresh state (number of
new message or Up to date).
Currently to mark a thread as read/unread, the user have to open a
dropdown
menu then click on the action... as this action can be done often, we
put it
directly in the thread action bar.
The thread query is an infinite one and the frontend logic is
based on the structuralSharing concept of react-query to
optimiscally update the react query cache on thread mutation in
order to improve ux. This part is a tricky one and it's easy to
introduce regression, that's why refactor it by moving the corresponding
logic into a mailbox-cache module, use a better naming (pin instead of optimistic)
and battle test it.
When the current view is filtering through a nested label, the thread
panel
title has no text because we only traverse root labels to try to find
the
one selected. Now we also recursively traverse children.
Add `NEXT_PUBLIC_FEEDBACK_WIDGET_HOME_CHANNEL` env var to be able to
set a specific channel id to receive feedback from unauthenticated
users.
For configuration ease and backward compatibility, if
`NEXT_PUBLIC_FEEDBACK_WIDGET_HOME_CHANNEL` is not set, the home feedback
widget fallback to `NEXT_PUBLIC_FEEDBACK_WIDGET_CHANNEL`.
A spoofed inbound with From == To was being marked is_sender=True via
the
`sender_email == recipient_email` shortcut in
_create_message_from_inbound.
Because MessageRecipient rows from the inbound path carry no
delivery_status, those messages matched retry_messages_task's
(is_sender=True AND delivery_status IN (RETRY, NULL)) filter and went
through send_message on every 5-min beat — DKIM-signing and re-emitting
the spam to every recipient on the envelope, externals included.
Legitimate self-sends are unaffected: send_message's internal redelivery
hits the mime_id dedup in deliver_inbound_message before reaching
_create_message_from_inbound, so the shortcut was already dead code on
the legitimate path.
The new widget loader consume `window._lasuite_widget` property to
know which widget to load. The previous version was using
`window._stmsg_header`. We refactor widget loading logic to support
both version with ease.
The new widget loader consume `window._lasuite_widget` property to
know which widget to load. The previous version was using
`window._stmsg_header`.
/!\ Update NEXT_PUBLIC_LAGAUFRE_WIDGET_PATH and
NEXT_PUBLIC_FEEDBACK_WIDGET_PATH
to target the new widget version before deploying this commit.
Add an option `--from-date` to the `search_reindex` management command.
In this way, in case of index task failure, we are able to reindex only
resources updated in a recent timeframe.
OpenSearch was returning 503/429 on delete_by_query under the load of
the periodic reindex. Each bulk_reindex_threads_task could fire up to
100 delete_by_query calls (one per chunk of 100 threads) to sweep
orphan messages, and bulk_delete_threads_task did one more to drop a
thread and all its children. delete_by_query holds a scroll context,
scans the index and refreshes per call — far heavier than the bulk
upserts running alongside it.
Tracking message deletes explicitly at signal time lets us replace
every hot-path delete_by_query with a bulk delete by _id:
- New search:pending_delete_messages set storing thread_id:message_id
pairs, fed by Message.post_delete (covers cascaded deletes too).
- New bulk_delete_messages_task issuing bulk DELETE actions with the
parent thread_id as routing.
- bulk_delete_threads_task rewritten to bulk DELETE thread parent docs
by _id; child message docs ride the new task via cascaded signals.
- _purge_orphan_docs and the per-chunk purge in reindex_bulk_threads
removed; reindex is now pure upsert.
Currently, we retry indexing task only on ConnectionError (socket-level)
but some error at http-level are also relevant to a retry.
So now we filter TransportError by status code: retryable (5xx + 429)
propagate so `bulk_reindex_threads_task` can autoretry with the existing
exponential backoff, 4xx stay swallowed since retrying caller bugs only
burns worker time.
Currently, when user has not its language set into local storage, we
retrieve
the default language through the navigator language. In some instance,
we would
like to enforce the default language. So we add a new env var
`NEXT_PUBLIC_FORCED_DEFAULT_LANGUAGE`, if this one is set to `true`, we
skip
the navigator.language and use the `NEXT_PUBLIC_DEFAULT_LANGUAGE` as
default
`bulk_data` method was missing some options (timeout, max_retries) so
sometimes when
the payload was heavy, the request can failed because the timeout was
too slow by default
(10s) and there is no `max_retries`. Furthermore, we build bulk payload
by chunking thread
but it did not check the payload bytes size, so in case of heavy
message, the payload could
be heavy. Now a max_bytes limit is set (50 Mib by default)
Opensearch index is updated each time a Thread, Message or MessageRecipient
is updated through signals. The current logic has performance issues has
n update of a resource will generate n celery task to update the same
resource... So this work aims to batch updates. Index is updated each
30s and resource ids is deduplicated to only update a resource once.
Furthermore, in an import context, the index will be updated only
when the import will be completed to prevent to spam the celery
worker with numerous indexation task.