Copy directly-sent e-mails to an IMAP Sent folder, with a live folder picker

A raw SMTP send has no server-side "Sent" copy on its own (unlike IMAP
drafts, which are inherently server-side) -- add an opt-in checkbox plus a
configurable target folder so directly sent Mahnungen/SEPA-info-mails still
show up in the account's Gesendet/Sent folder like a normal mail client
would leave them. Applies only to "send" delivery; drafts already live on
the server by definition.

Both the Entwürfe- and Gesendet-folder fields are now editable comboboxes:
a new "Ordnerliste laden" button fetches the real folder list from the IMAP
server (needs working credentials first) via LIST, decoding folder names
from modified UTF-7 (RFC 3501) so names like "Entwürfe" render correctly
instead of as "Entw&APw-rfe". Free text still works -- ensure_imap_folder()
creates the folder on first use if it doesn't exist yet, checked once per
batch rather than before every single message.

mail_delivery.append_to_imap_drafts() became the more general
append_message(client, content, folder=, flags=), reused for both the
\Draft and \Seen cases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marcel Peterkau
2026-08-15 04:34:34 +02:00
co-authored by Claude Sonnet 5
parent 6b0da82b45
commit cc4aaef895
9 changed files with 505 additions and 28 deletions
+29
View File
@@ -12,6 +12,8 @@ def test_email_settings_default_to_local_delivery_for_new_repositories(tmp_path)
assert settings["delivery_mode"] == "local"
assert settings["smtp_host"] == ""
assert settings["imap_host"] == ""
assert settings["imap_sent_enabled"] is False
assert settings["imap_sent_folder"] == "INBOX.Sent"
def test_email_settings_round_trip_through_save_and_get(tmp_path) -> None:
@@ -31,6 +33,8 @@ def test_email_settings_round_trip_through_save_and_get(tmp_path) -> None:
imap_username="board@example.org",
imap_password="secret",
imap_drafts_folder="INBOX.Entwürfe",
imap_sent_enabled=True,
imap_sent_folder="INBOX.Gesendet",
)
settings = repository.get_email_settings()
@@ -39,6 +43,31 @@ def test_email_settings_round_trip_through_save_and_get(tmp_path) -> None:
assert settings["smtp_port"] == 587
assert settings["smtp_password"] == "secret"
assert settings["imap_drafts_folder"] == "INBOX.Entwürfe"
assert settings["imap_sent_enabled"] is True
assert settings["imap_sent_folder"] == "INBOX.Gesendet"
def test_email_settings_sent_copy_requires_imap_host_even_in_send_only_mode(tmp_path) -> None:
repository = MemberRepository(tmp_path)
repository.initialize()
with pytest.raises(RepositoryError, match="Gesendet-Ordner ist ein IMAP-Server erforderlich"):
repository.save_email_settings(
delivery_mode="send",
smtp_host="mail.example.org",
smtp_port=587,
smtp_security="starttls",
smtp_username="",
smtp_password="",
imap_host="",
imap_port=993,
imap_security="ssl",
imap_username="",
imap_password="",
imap_drafts_folder="",
imap_sent_enabled=True,
imap_sent_folder="",
)
@pytest.mark.parametrize(