""" Job arq: sync_mailbox – trigger manuale per forzare la sincronizzazione di una casella. Questo job viene usato per: - Forzare una sync immediata dopo la creazione di una nuova casella - Resync manuale da parte dell'admin - Retry dopo un errore (called dal pool monitor) Non sostituisce il loop IMAP continuo (IMAPConnection); è un one-shot job. Sincronizza sia INBOX (per rilevare ricevute PEC e messaggi in arrivo) sia la cartella Sent (per aggiornare imap_uid sul record send_pec ed evitare duplicati outbound che rompono il binding delle ricevute). """ import logging from typing import Any from app.database import AsyncSessionLocal from app.imap.sync import sync_new_messages, sync_sent_messages from app.models import Mailbox logger = logging.getLogger(__name__) async def sync_mailbox(ctx: dict[str, Any], mailbox_id: str) -> dict: """ Job arq: sincronizza una singola casella PEC (INBOX + Sent). Args: ctx: contesto arq (contiene redis, pool reference) mailbox_id: UUID della casella da sincronizzare Returns: dict con risultato del job """ redis_client = ctx.get("redis") async with AsyncSessionLocal() as db: mailbox = await db.get(Mailbox, mailbox_id) if not mailbox: return {"status": "error", "message": f"Mailbox {mailbox_id} non trovata"} if mailbox.status not in ("active", "error"): return { "status": "skipped", "message": f"Mailbox status={mailbox.status}, skip", } from app.imap.connection import IMAPConnection creds = IMAPConnection._decrypt_creds(mailbox) try: conn = IMAPConnection(mailbox_id=mailbox_id, redis_client=redis_client) client = await conn._connect(creds) # Sync INBOX: ricevute PEC e messaggi in arrivo n_inbox = await sync_new_messages(client, mailbox, db, redis_client) # Sync Sent: aggiorna imap_uid sui record send_pec e previene duplicati. # Fondamentale per il corretto binding delle ricevute PEC successive. n_sent = 0 try: n_sent = await sync_sent_messages(client, mailbox, db, redis_client) except Exception as sent_err: logger.warning( f"[sync_mailbox] {mailbox.email_address} errore sync Sent " f"(non critico): {sent_err}" ) try: await client.logout() except Exception: pass logger.info( f"[sync_mailbox] {mailbox.email_address}: " f"INBOX={n_inbox} nuovi, Sent={n_sent} nuovi" ) return { "status": "ok", "mailbox": mailbox.email_address, "new_messages_inbox": n_inbox, "new_messages_sent": n_sent, } except Exception as e: logger.error(f"[sync_mailbox] {mailbox_id} errore: {e}", exc_info=True) return { "status": "error", "mailbox": mailbox.email_address if mailbox else mailbox_id, "message": str(e), }