feat: replace SendGrid with Gmail API via Domain-Wide Delegation
Some checks are pending
Check Python Version Consistency / Check Python Version (push) Waiting to run
Some checks are pending
Check Python Version Consistency / Check Python Version (push) Waiting to run
This commit is contained in:
parent
e744a1d510
commit
bc0ee3b3ee
50
main.py
50
main.py
|
|
@ -57,8 +57,7 @@ from functools import wraps
|
||||||
from starlette.middleware.sessions import SessionMiddleware
|
from starlette.middleware.sessions import SessionMiddleware
|
||||||
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
|
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
|
||||||
from authlib.integrations.starlette_client import OAuth
|
from authlib.integrations.starlette_client import OAuth
|
||||||
import sendgrid
|
|
||||||
from sendgrid.helpers.mail import Mail
|
|
||||||
|
|
||||||
AGENT_ID = "osvauco-opax"
|
AGENT_ID = "osvauco-opax"
|
||||||
PROJECT_ID = os.environ.get("GOOGLE_CLOUD_PROJECT", "propane-will-491900-m5")
|
PROJECT_ID = os.environ.get("GOOGLE_CLOUD_PROJECT", "propane-will-491900-m5")
|
||||||
|
|
@ -305,7 +304,7 @@ async def vm_ssh_key(instance: str = "osvauco-dev-vm"):
|
||||||
@app.get("/notify/channels")
|
@app.get("/notify/channels")
|
||||||
async def get_notify_channels():
|
async def get_notify_channels():
|
||||||
return {"channels": [
|
return {"channels": [
|
||||||
{"type": "email", "configured": bool(os.getenv("SENDGRID_API_KEY")), "target": os.getenv("NOTIFY_EMAIL_TO")},
|
{"type": "email", "configured": bool(os.getenv("GMAIL_DEFAULT_SENDER")), "target": os.getenv("NOTIFY_EMAIL_TO")},
|
||||||
{"type": "sms", "configured": bool(os.getenv("TWILIO_ACCOUNT_SID")), "target": os.getenv("TWILIO_FROM_NUMBER")},
|
{"type": "sms", "configured": bool(os.getenv("TWILIO_ACCOUNT_SID")), "target": os.getenv("TWILIO_FROM_NUMBER")},
|
||||||
{"type": "webhook", "configured": bool(os.getenv("NOTIFY_WEBHOOK_URL")), "url": os.getenv("NOTIFY_WEBHOOK_URL")},
|
{"type": "webhook", "configured": bool(os.getenv("NOTIFY_WEBHOOK_URL")), "url": os.getenv("NOTIFY_WEBHOOK_URL")},
|
||||||
]}
|
]}
|
||||||
|
|
@ -473,20 +472,49 @@ class NotificationService:
|
||||||
return {"status": "error", "detail": str(e)}
|
return {"status": "error", "detail": str(e)}
|
||||||
|
|
||||||
async def send_email(self, req) -> dict:
|
async def send_email(self, req) -> dict:
|
||||||
api_key = os.environ.get("SENDGRID_API_KEY")
|
import base64
|
||||||
|
import os
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from google.auth import default as google_auth_default
|
||||||
|
from googleapiclient.discovery import build
|
||||||
|
|
||||||
to_email = req.to or ALERT_EMAIL
|
to_email = req.to or ALERT_EMAIL
|
||||||
if not api_key:
|
|
||||||
return {"status": "not_configured", "detail": "SENDGRID_API_KEY mangler"}
|
|
||||||
if not to_email:
|
if not to_email:
|
||||||
return {"status": "not_configured", "detail": "Ingen mottaker-e-post"}
|
return {"status": "skipped", "reason": "no recipient"}
|
||||||
|
|
||||||
|
allowed_senders_str = os.getenv("GMAIL_ALLOWED_SENDERS", "")
|
||||||
|
allowed = [s.strip() for s in allowed_senders_str.split(",") if s.strip()]
|
||||||
|
sender = os.getenv("GMAIL_DEFAULT_SENDER")
|
||||||
|
|
||||||
|
if not sender or not allowed:
|
||||||
|
return {"status": "not_configured", "reason": "GMAIL_DEFAULT_SENDER or GMAIL_ALLOWED_SENDERS not set"}
|
||||||
|
|
||||||
|
if sender not in allowed:
|
||||||
|
return {"status": "error", "reason": f"Default sender {sender} is not in the allowed list"}
|
||||||
|
|
||||||
meta = self._event_meta(req.event)
|
meta = self._event_meta(req.event)
|
||||||
subject = req.subject or f"{meta['emoji']} CostGuard: {meta['label']} — {datetime.date.today()}"
|
subject = req.subject or f"{meta['emoji']} CostGuard: {meta['label']} — {datetime.date.today()}"
|
||||||
html_content = req.body_html if req.body_html else await self._build_digest_html(req.payload)
|
html_content = req.body_html if req.body_html else await self._build_digest_html(req.payload)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = Mail(from_email="costguard@osvauco.no", to_emails=to_email, subject=subject, html_content=html_content)
|
credentials, _ = google_auth_default(scopes=["https://www.googleapis.com/auth/gmail.send"])
|
||||||
sg = sendgrid.SendGridAPIClient(api_key)
|
credentials = credentials.with_subject(sender)
|
||||||
response = sg.send(message)
|
|
||||||
return {"status": "ok", "message_id": response.headers.get("X-Message-Id", ""), "to": to_email, "event": req.event}
|
service = build("gmail", "v1", credentials=credentials)
|
||||||
|
|
||||||
|
message = MIMEMultipart("alternative")
|
||||||
|
message["to"] = to_email
|
||||||
|
message["from"] = sender
|
||||||
|
message["subject"] = subject
|
||||||
|
message.attach(MIMEText(html_content, "html"))
|
||||||
|
|
||||||
|
raw = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
||||||
|
result = service.users().messages().send(
|
||||||
|
userId="me", body={"raw": raw}
|
||||||
|
).execute()
|
||||||
|
|
||||||
|
return {"status": "ok", "message_id": result.get("id", ""), "to": to_email, "event": req.event}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"status": "error", "detail": str(e)}
|
return {"status": "error", "detail": str(e)}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user