""" EmailMessagingGraph.py ---------------------- Private Microsoft Graph mail sender Application permissions, shared mailbox """ import base64 import msal import requests from functools import lru_cache from pathlib import Path from typing import Union, List # ========================= # PRIVATE CONFIG (ONLY YOU) # ========================= TENANT_ID = "7d269944-37a4-43a1-8140-c7517dc426e9" CLIENT_ID = "4b222bfd-78c9-4239-a53f-43006b3ed07f" CLIENT_SECRET = "Txg8Q~MjhocuopxsJyJBhPmDfMxZ2r5WpTFj1dfk" SENDER = "reports@buzalka.cz" AUTHORITY = f"https://login.microsoftonline.com/{TENANT_ID}" SCOPE = ["https://graph.microsoft.com/.default"] @lru_cache(maxsize=1) def _get_token() -> str: app = msal.ConfidentialClientApplication( CLIENT_ID, authority=AUTHORITY, client_credential=CLIENT_SECRET, ) token = app.acquire_token_for_client(scopes=SCOPE) if "access_token" not in token: raise RuntimeError(f"Graph auth failed: {token}") return token["access_token"] def send_mail( to: Union[str, List[str]], subject: str, body: str = "", *, html: bool = False, attachments: Union[str, Path, List[Union[str, Path]], None] = None, ): """ Send email via Microsoft Graph. :param to: email or list of emails :param subject: subject :param body: email body (default empty) :param html: True = HTML, False = plain text :param attachments: file path or list of file paths to attach """ if isinstance(to, str): to = [to] if attachments is None: attachments = [] elif isinstance(attachments, (str, Path)): attachments = [attachments] attachment_payloads = [] for path in attachments: path = Path(path) attachment_payloads.append({ "@odata.type": "#microsoft.graph.fileAttachment", "name": path.name, "contentType": "application/octet-stream", "contentBytes": base64.b64encode(path.read_bytes()).decode(), }) payload = { "message": { "subject": subject, "body": { "contentType": "HTML" if html else "Text", "content": body, }, "toRecipients": [ {"emailAddress": {"address": addr}} for addr in to ], **({"attachments": attachment_payloads} if attachment_payloads else {}), }, "saveToSentItems": "true", } headers = { "Authorization": f"Bearer {_get_token()}", "Content-Type": "application/json", } r = requests.post( f"https://graph.microsoft.com/v1.0/users/{SENDER}/sendMail", headers=headers, json=payload, timeout=30, ) if r.status_code != 202: raise RuntimeError( f"sendMail failed [{r.status_code}]: {r.text}" )