Sender avatars, PDF password preview, contacts autosave, account-view + Trash/Spam auto-purge, UI polish
Sender avatars (privacy-safe, server-fetched, cached): - New GET /api/avatar resolves Gravatar (d=404) then the sender domain's favicon under the SSRF guard, Redis-cached (hit+miss); gated by SENDER_AVATARS_ENABLED. GuardedResponse gains raw bytes. Shown in the mail list, reading pane, People panel, and Contacts (overlay that falls back to initials on a miss). PDF preview: - Password-protected PDFs now prompt in-place and retry (pdf.js PasswordException), with a clear error for other load failures instead of a blank overlay. The password is remembered per attachment until the thread is left; clicking the field no longer dismisses the overlay. Contacts: - Autosave reply recipients: message.sent now carries to/cc (name+email); the contacts plugin subscribes and adds any non-contact recipients to the Autosaved group. Contact avatars show the gravatar/favicon via /api/avatar. Mail views / retention: - Clicking an account shows all its mail except Trash/Spam (was inbox-only). - Daily cron autopurge_trash_spam permanently deletes Trash/Spam older than TRASH_RETENTION_DAYS (default 30; 0 disables) — server EXPUNGE + local rows. UI polish: - Ultimo signet logo in the Mail/Calendar/Contacts sidebar header and collapsed rail; consistent 11px top margin and vertically-aligned header row across states (fixed-size flex icon buttons); SVG favicon on the browser tab. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U72FZiF1W1pb8XxyWBTREA
This commit is contained in:
@@ -21,13 +21,19 @@ import logging
|
||||
import signal
|
||||
from typing import Any
|
||||
|
||||
from arq import cron
|
||||
from arq.worker import Worker
|
||||
|
||||
from app.config import get_settings
|
||||
from app.queue import QUEUE_BULK, QUEUE_INTERACTIVE, redis_settings
|
||||
from app.sync.actions import autopurge_trash_spam
|
||||
from app.sync.coordinator import SyncCoordinator
|
||||
from workers.worker import BULK_FUNCTIONS, INTERACTIVE_FUNCTIONS
|
||||
|
||||
# Core scheduled jobs. Daily Trash/Spam retention purge at 03:17 (off-peak, and a
|
||||
# minute that won't collide with typical top-of-hour crons).
|
||||
_CORE_CRON_JOBS = [cron(autopurge_trash_spam, hour=3, minute=17)]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_SHUTDOWN_DRAIN_S = 10.0 # §7: release leases + drain within this budget
|
||||
@@ -67,7 +73,7 @@ def build_arq_workers(role: str) -> list[Worker]:
|
||||
QUEUE_INTERACTIVE,
|
||||
s.interactive_concurrency,
|
||||
120,
|
||||
cron_jobs=plugin_cron_jobs(),
|
||||
cron_jobs=[*_CORE_CRON_JOBS, *plugin_cron_jobs()],
|
||||
),
|
||||
_worker(BULK_FUNCTIONS, QUEUE_BULK, s.bulk_concurrency, 3600),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user