It is only doing SQL, so does not need to be async.
This way insert_into_smtp() can use a single transaction
to insert a tombstone and enqueue the message.
as recently discussed with @adbenitez and @Amzd, it is better to add an
info message also for unpinning a message:
otherwise the message "Alice pinned a message" may look like a bug if
there is nothing actually pinned as Bob unpinned for whatever reason
afterwards.
technically, this is a simple change (famous last words) as a messages
were always sent on pinning as well as on unpinning, but for unpinning,
they were additionally hidden.
now the "unpin" info message is visible, however, still tuned down: it
is not notified and it does not bubble up in the chatlist (both similar
to many info messages about group changes)
ftr, this is a minor change, and it should not block ongoing releases
wrt pinned messages
join_next() failing means something went really wrong, e.g. panic inside a task.
We also don't cancel the tasks other than by dropping JoinSet,
so join_next() should never return an error.
It removes compromised emSign CA,
see https://emudhra.we-have-a-problem.com/ for details.
We have duplicate dependency because of iroh 0.35
still dependencing on the old version,
but most important is that IMAP/SMTP connections
are using the new version.
spec.md already documents Header Confidentiality Policy
that hides Chat-Version header in encrypted messages,
but so far we have copied the header into unprotected headers for compatibility.
Since 2.45.0 prefetch does not look at the unprotected Chat-Version
do decide if the message should be downloaded
(change b94792706a)
and since 2.50 show_emails setting which controlled
whether only "chat messages" are processed
is removed (change 63596a4940).
Without connection candidates IMAP busy loops when you try to stop I/O
because interrupt channel gets closed and recv() always returns an error immediately,
but no connection is attempted afterwards so IMAP loop keeps calling .prepare()
in a loop without ever yielding control to executor, so it cannot even be cancelled.
New JSON-RPC test test_pseudo_transport_start_stop_io reproduces the problem
from CFFI test tests/test_3_offline.py::TestOfflineChat::test_send_lots_of_offline_msgs
which added one transport without connection candidates and started I/O.
A reaction summary of a broadcast channel is sent as an empty reaction
to the latest channel message. When another device of the channel owner
received it, it removed the owner's own reaction on that message and
queued the message for the next summary. So the owner's devices sent
the summary to each other, and to all subscribers, every 10 minutes
forever.
self.parts.remove(i) can panic if "i" is out of range,
but now it is obvious that it will not because "i" is returned from position(),
and clippy is happy.
This is a minor fix, worst case an image with very large dimensions will not be classified as a File.
Mostly removing `clippy::arithmetic_side_effects`.
while showing reactions for channel summaries was regarded as "let's
try" when channel reactions were introduced, meanwhile, after some
experience, it only clutters and spams the chatlist.
for the subscriber, this is anyways only "you reacted", which is often
wrong as there are other reactions. for the owner it is about some names
they do not know. the idea of having some "feedback" for the owner is
still fulfilled as reactions are (optionally) notified - so owner can
still have the "ping ping ping" and relax :)
finallly, this is also what telegram is doing with their channels
Also moved python version to environment variable
so status checks will be named
"JSON-RPC Python tests (ubuntu-latest, latest)" instead of
"JSON-RPC Python tests (ubuntu-latest, 3.14)" and we don't have to
update the list of required status checks each time the version changes.
We already do the same for Rust.
Remove the two unnecessary DB calls that fetch the message
(which could possibly be different from the current `Message`).
Now the only async thing in `get_summary_text_ext` that remains
is `get_webxdc_info`.
See https://github.com/chatmail/core/pull/8826.
The timeout currently owns each IMAP and SMTP JoinHandle. When 30
seconds elapse, dropping the timeout drops the handle and detaches the
task instead of cancelling it. On Haiku, a network task stuck behind a
missed or delayed socket wakeup can therefore outlive scheduler
shutdown, retaining connections and interfering with restart or
active-account deletion.
Borrow each handle while waiting, then explicitly abort and await it if
shutdown times out. This guarantees that scheduler shutdown does not
complete while a timed-out network task is still running.
Classic email servers sometimes start rejecting all messages and ask the user to solve web CAPTCHA, but still allow to connect.
In this case we want to switch to another SMTP server by moving the transport to the end of the list returned by smtp::sorted_transports.
We disconnect after any message sending failure, so next message or resent same message will be sent over another other transport.
Permanent SMTP error does not necessarily mean that switching to another server will help.
For example if the user tries to send a large message, any server will reject it, and it is not a failure of the server.
We do not try to distinguish between error messages, so it is expected that we may
sometimes switch to another server when it is unnecessary.
Reencoding animated WebPs into JPEG loses all frames except the first one.
Avatars we still reencode because we don't want animated avatars anyway.
animated.webp is produced with this script using Pillow 12.3.0:
```
#!/usr/bin/env python3
from PIL import Image, ImageOps
with Image.open("avatar900x900.png") as im:
frames = [im.rotate(x) for x in range(10, 360, 10)]
im.save(
"animated.webp",
"webp",
append_images=frames,
lossless=True,
duration=200,
save_all=True,
)
```
Resulting image is 677K, just slightly larger than the size needed to trigger reencoding.
The test fails on main with ~150MB memory remaining allocated
after sending/receiving two 20MB messages,
and stays well below when explicitely setting libc's M_MMAP_THRESHOLD.
this PR adds a new `msg_get_summary_text(add_forwarded, add_type_emoji,
approx_chars)` api, replacing the old `get_summarytext(approx_chars)`.
checking the concrete existing and new usecases, the new options are
required, eg. for:
- pinned messages: they do not show the user name (therefore, there
should not be a "forward" indicator), and may or may not not e.g.
display image left of string (this makes type emoji optional)
- notifications, quotes: they show user name (therefore, "forward"
indicator needed) and often display e.g. image a bit off (so type emoji
is potentially useful, cmp. whatsapp, but potentially also optional)
- copy to clipboard: clipboard should get forwarded and emoji, but text
should not be truncated and linends should be preserved - esp. the
latter was a silent bug in at least android. to support that better,
`approx_chars` can be set to `0` to skip truncation and unwrapping.
| Use case | `add_forwarded` | `add_type_emoji` | `approx_chars>0` |
| --- | :---: | :---: | :---: |
| Notification | ✅ | ✅ (potentially ❌) | ✅ |
| Quote | ✅ | ✅ (potentially ❌) | ✅ |
| Pinned message | ❌ | ❌ (rarely ✅) | ✅ |
| Copy to clipboard | ✅ | ✅ | ❌ |
| Chatlist (core internal) | ✅ | ✅ | ✅ |
| Reaction summary (core internal) | ❌ | ✅ | ✅ |
(the get_summary() returning a "lot" can maybe be removed at some point,
in favor of adding the usernames directly to get_summary_text() and
changing add_forwarded to add_usernames. but that is out of scope)
jsonrpc can be added easily once we settle on name etc.
references: https://github.com/chatmail/core/pull/8812 ,
https://github.com/chatmail/core/pull/8546
cc @Amzd , @adbenitez
---------
Co-authored-by: Hocuri <hocuri@gmx.de>
mail-builder changed how such filename is encoded in Content-Disposition between versions 0.5.0 and 1.0.0.
We can parse both encodings already and the test was not failing, but it is worth adding this corner case to the tests.
mail-builder 0.5.0 encoded the filename like this:
Content-Disposition: attachment;
filename="=?utf-8?Q?very_long_filename_with_emoji_=F0=9F=98=80_very_long_file_name_with_emoji_=F0=9F=AB=A0_very_long_file_name_with_emoji.tar.gz?="
mail-builder 1.0.0 encodes the filename like this:
Content-Disposition: attachment;
filename*0*=UTF-8''very%20long%20filename%20with%20emoji%20%F0%9F%98%80%20;
filename*1*=very%20long%20file%20name%20with%20emoji%20%F0%9F%AB%A0%20very;
filename*2*=%20long%20file%20name%20with%20emoji.tar.gz
async-imap 0.12.0 uses imap-proto 0.17.0 which includes bugfixes
for parsing responses with unicode characters.
Also upgraded base64 to 0.23 since new async-imap depends on it anyway.
`dc_chatlist_get_summary2()` was introduced for desktop that time,
but then replaced by jsonrpc's `get_chat_list_item_by_id()`.
outside desktop,
the cffi function seems to be never used in the past nor is today.
@adbenitez @Amzd - can you confirm it is not used?
similar for `summaryPreviewImage` - that was used on desktop, and is now
unused member of jsonrpc's ChatListItemFetchResult. question here: i
assume that it is fine to remove the member, and that desktop (maybe
after rebuilding) is fine with that.
@WofWca @nicodh - can you confirm, desktop is fine with the removal of
the member?
for general cleanup,
in an anyway complex area, it makes sense to remove these APIs.
Every `info!`, `warn!` and `error!` call expanded a complete
`tracing::event!` to mirror its message into `tracing` (#6919),
and that mirror made up most of core's macro output, now removed:
it's down from 219k to 54k lines using
RUSTC_BOOTSTRAP=1 cargo rustc -p deltachat --lib \
--profile check -- -Zmacro-stats
A warm build and `touch src/lib.rs` with rustc 1.99.0,
CARGO_INCREMENTAL=0 RUSTC_WRAPPER= /usr/bin/time -v \
cargo check -p deltachat
takes about a third less time and 0.37 GiB less peak memory,
`cargo build -p deltachat` about a tenth less time and 0.36 GiB less.
The release binary from `nix build .#deltachat-rpc-server-x86_64-linux`
gets 2.7% smaller.
If we want to use tracing events to integrate better with iroh-debugging,
for example when we move to iroh 1.X,
we could introduce some iroh-relevant tracing events in core.
Since #8350 (2026-09-09) deltachat-jsonrpc-bindings build-depends
on deltachat-jsonrpc, so clippy, nextest and the doctests,
which select the whole workspace, compiled core
and its whole dependency tree a second time for the host.
The lint job also checked every dependency twice, once per panic strategy,
and repeated clippy's checks in a separate all-features `cargo check`.
With a warm cache on 4 CPUs on my machine, the lint job drops
from 150 to 60 seconds and the tests build
from 100 to 75 seconds.
Peak memory falls for lint from
5.1 to 3.5 GiB and for tests
7.5 to 5.4 GiB.
When connecting to SMTP, transports are now tried
from the most recently successfully used transport to the least recently used.
Transports that were never used for sending
are tried in the order of increasing ID because init_transports()
tries to select the fastest transport for the first one.
This solves the problem of having to wait for timeout each time
if the first tried transport is down.
RPC server subprocess may exit for various reasons,
e.g. because of panic or because of debug_assert! during testing,
or because the process is killed manually or by OOM killer.
We still want to shutdown all threads cleanly in this case
and not fail in Rpc.close(). Otherwise Python process cannot exit
as it waits forever for all threads to terminate.
Without the fix, when added test is run in pytest,
pytest prints "ERROR at teardown of test_shutdown" and gets stuck.
When pytest is manually terminated with ^C, the following error is printed:
```
Exception ignored while joining a thread in _thread._shutdown():
Traceback (most recent call last):
File "/usr/lib/python3.14/threading.py", line 1583, in _shutdown
_thread_shutdown()
KeyboardInterrupt:
```
_thread_shutdown() is the function waiting for all non-daemon threads to finish.