Resout le probleme de firewall SMTP corporate qui bloque outbound 25/465/587 depuis les postes de travail. Outlook utilise HTTPS (EWS/Graph) vers O365 -> aucun firewall. Service mail_outlook_com.py: - send_via_outlook_com(to, subject, html, cc, display_only): pilote Outlook via COM pywin32. Mail apparait dans Sent Items du user, traçable. - Mode display_only=True ouvre la fenetre de composition Outlook au lieu d'envoyer automatiquement (relecture manuelle). - pythoncom.CoInitialize() pour fonctionner dans un thread uvicorn. Service mail_service.py: - send_html_mail dispatche selon Setting 'mail_backend' (smtp / outlook_com). - Defaut = smtp. Settings: - 'mail_backend' = 'smtp' | 'outlook_com' - 'mail_outlook_display_only' = 'true' | 'false' - UI: 2 selects en haut de la card SMTP avec hints + le SMTP existant en dessous Pre-requis Windows: pip install pywin32 + Outlook lance + connecte au compte O365.
74 lines
2.7 KiB
Python
74 lines
2.7 KiB
Python
"""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
|