feat(email): SMTP fallback via OVH Zimbra + direct SMTP for @nordabiznes.pl
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Domain nordabiznes.pl removed from M365 (moved to OVH Zimbra). Graph API can no longer send as @nordabiznes.pl. - Added _send_via_smtp() method using SMTP_HOST/SMTP_USER/SMTP_PASSWORD - @nordabiznes.pl sender goes directly to SMTP (skips Graph API) - Other domains still try Graph API first, SMTP as fallback - .env updated with OVH Zimbra SMTP credentials Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cb1409bf09
commit
933e062196
@ -107,6 +107,12 @@ class EmailService:
|
|||||||
Returns:
|
Returns:
|
||||||
True if sent successfully, False otherwise
|
True if sent successfully, False otherwise
|
||||||
"""
|
"""
|
||||||
|
# If sender is @nordabiznes.pl, go directly to SMTP (domain removed from M365)
|
||||||
|
sender = from_address or self.mail_from
|
||||||
|
if sender and '@nordabiznes.pl' in sender:
|
||||||
|
logger.info(f"Sender is @nordabiznes.pl — using SMTP directly (not Graph API)")
|
||||||
|
return self._send_via_smtp(subject, body_html or body_text, to, sender_name=sender_name, bcc=bcc)
|
||||||
|
|
||||||
if not MSAL_AVAILABLE:
|
if not MSAL_AVAILABLE:
|
||||||
logger.error("msal package not available - cannot send email")
|
logger.error("msal package not available - cannot send email")
|
||||||
return False
|
return False
|
||||||
@ -159,7 +165,7 @@ class EmailService:
|
|||||||
bcc_recipients = [{"emailAddress": {"address": email}} for email in bcc]
|
bcc_recipients = [{"emailAddress": {"address": email}} for email in bcc]
|
||||||
email_msg["message"]["bccRecipients"] = bcc_recipients
|
email_msg["message"]["bccRecipients"] = bcc_recipients
|
||||||
|
|
||||||
# Send email via Graph API
|
# Try Graph API first
|
||||||
url = f"{self.graph_endpoint}/users/{sender}/sendMail"
|
url = f"{self.graph_endpoint}/users/{sender}/sendMail"
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {token}",
|
"Authorization": f"Bearer {token}",
|
||||||
@ -169,14 +175,62 @@ class EmailService:
|
|||||||
response = requests.post(url, headers=headers, json=email_msg, timeout=30)
|
response = requests.post(url, headers=headers, json=email_msg, timeout=30)
|
||||||
|
|
||||||
if response.status_code == 202:
|
if response.status_code == 202:
|
||||||
logger.info(f"Email sent successfully to {to}")
|
logger.info(f"Email sent successfully via Graph API to {to}")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logger.error(f"Failed to send email. Status: {response.status_code}, Response: {response.text}")
|
logger.error(f"Failed to send email via Graph API. Status: {response.status_code}, Response: {response.text}")
|
||||||
return False
|
# Fallback to SMTP if Graph API fails (e.g. domain removed from M365)
|
||||||
|
logger.info("Attempting SMTP fallback...")
|
||||||
|
return self._send_via_smtp(subject, body, to, sender_name=sender_name, bcc=bcc)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Exception during email sending: {e}", exc_info=True)
|
logger.error(f"Exception during Graph email sending: {e}", exc_info=True)
|
||||||
|
# Fallback to SMTP
|
||||||
|
logger.info("Attempting SMTP fallback after exception...")
|
||||||
|
return self._send_via_smtp(subject, body, to, sender_name=sender_name, bcc=bcc)
|
||||||
|
|
||||||
|
def _send_via_smtp(self, subject, body, to, sender_name=None, bcc=None):
|
||||||
|
"""Fallback: send email via SMTP (OVH Zimbra)."""
|
||||||
|
import smtplib
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.utils import formataddr
|
||||||
|
|
||||||
|
smtp_host = os.getenv('SMTP_HOST', 'ssl0.ovh.net')
|
||||||
|
smtp_port = int(os.getenv('SMTP_PORT', '587'))
|
||||||
|
smtp_user = os.getenv('SMTP_USER', '')
|
||||||
|
smtp_pass = os.getenv('SMTP_PASSWORD', '')
|
||||||
|
mail_from = os.getenv('MAIL_FROM', smtp_user)
|
||||||
|
mail_from_name = sender_name or os.getenv('MAIL_FROM_NAME', 'NordaBiznes Portal')
|
||||||
|
|
||||||
|
if not smtp_user or not smtp_pass:
|
||||||
|
logger.error("SMTP fallback failed: SMTP_USER or SMTP_PASSWORD not configured")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg = MIMEMultipart('alternative')
|
||||||
|
msg['Subject'] = subject
|
||||||
|
msg['From'] = formataddr((mail_from_name, mail_from))
|
||||||
|
msg['To'] = ', '.join(to) if isinstance(to, list) else to
|
||||||
|
if bcc:
|
||||||
|
msg['Bcc'] = ', '.join(bcc) if isinstance(bcc, list) else bcc
|
||||||
|
|
||||||
|
msg.attach(MIMEText(body, 'html', 'utf-8'))
|
||||||
|
|
||||||
|
all_recipients = list(to) if isinstance(to, list) else [to]
|
||||||
|
if bcc:
|
||||||
|
all_recipients.extend(bcc if isinstance(bcc, list) else [bcc])
|
||||||
|
|
||||||
|
with smtplib.SMTP(smtp_host, smtp_port, timeout=30) as server:
|
||||||
|
server.starttls()
|
||||||
|
server.login(smtp_user, smtp_pass)
|
||||||
|
server.sendmail(mail_from, all_recipients, msg.as_string())
|
||||||
|
|
||||||
|
logger.info(f"Email sent successfully via SMTP to {to}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"SMTP fallback failed: {e}", exc_info=True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user