"""Backend mail via Outlook COM (pywin32) — Windows local uniquement. Permet d'envoyer un mail via la session Outlook ouverte sur le poste qui fait tourner PatchCenter. Aucun firewall SMTP à ouvrir : Outlook utilise HTTPS (EWS/Graph) pour parler à O365. Usage : nécessite Outlook lancé + connecté + pywin32 installé. Mode `display_only=True` : ouvre la fenêtre de composition pré-remplie au lieu d'envoyer automatiquement (utile pour relecture finale). """ import logging log = logging.getLogger("patchcenter.mail.outlook_com") def send_via_outlook_com(to, subject, html, cc=None, display_only=False): """Envoie un mail HTML via Outlook COM. Retourne {ok, msg, sent_to}.""" try: import win32com.client # pywin32 import pythoncom # pour CoInitialize en thread non-main except ImportError as e: return {"ok": False, "msg": f"pywin32 non installé sur le serveur PatchCenter ({e}). " f"`pip install pywin32` dans le venv puis restart uvicorn."} if isinstance(to, str): to = [to] to = [a.strip() for a in to if a and a.strip()] if not to: return {"ok": False, "msg": "Destinataire(s) vide(s)"} if cc is None: cc = [] if isinstance(cc, str): cc = [cc] cc = [a.strip() for a in cc if a and a.strip()] # CoInitialize est nécessaire dans un thread (uvicorn worker = thread non-main) try: pythoncom.CoInitialize() except Exception: pass try: outlook = win32com.client.Dispatch("Outlook.Application") mail = outlook.CreateItem(0) # 0 = olMailItem mail.To = "; ".join(to) if cc: mail.CC = "; ".join(cc) mail.Subject = subject mail.HTMLBody = html if display_only: mail.Display(False) # False = non-modal (ouvre dans une fenêtre) return {"ok": True, "msg": f"Mail ouvert dans Outlook pour relecture (clique Envoyer dans la fenêtre)", "sent_to": to, "cc": cc, "backend": "outlook_com", "mode": "display"} mail.Send() return {"ok": True, "msg": f"Envoyé via Outlook à {', '.join(to)}" + (f" + CC {len(cc)}" if cc else ""), "sent_to": to, "cc": cc, "backend": "outlook_com", "mode": "send"} except Exception as e: # Erreurs courantes : # - "Aucune instance Outlook" : Outlook pas lancé # - "Operation aborted" : prompt sécurité refusé par utilisateur return {"ok": False, "msg": f"Outlook COM: {type(e).__name__}: {e}", "backend": "outlook_com"} finally: try: pythoncom.CoUninitialize() except Exception: pass