95 lines
3.0 KiB
Python
95 lines
3.0 KiB
Python
|
|
"""SendGrid email provider implementation."""
|
||
|
|
import logging
|
||
|
|
|
||
|
|
import requests
|
||
|
|
from flask import current_app
|
||
|
|
|
||
|
|
from gatehouse_app.services.email_provider import EmailMessage, EmailProvider
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
|
||
|
|
class SendGridEmailProvider(EmailProvider):
|
||
|
|
"""SendGrid API-based email provider implementation."""
|
||
|
|
|
||
|
|
# Configuration keys
|
||
|
|
SENDGRID_API_KEY = "SENDGRID_API_KEY"
|
||
|
|
SENDGRID_FROM_EMAIL = "SENDGRID_FROM_EMAIL"
|
||
|
|
FROM_ADDRESS = "FROM_ADDRESS"
|
||
|
|
|
||
|
|
API_URL = "https://api.sendgrid.com/v3/mail/send"
|
||
|
|
|
||
|
|
def send(self, message: EmailMessage) -> bool:
|
||
|
|
"""Send an email via SendGrid API.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
message: EmailMessage instance containing email details
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
bool: True if email was sent successfully, False otherwise
|
||
|
|
"""
|
||
|
|
api_key = current_app.config.get(self.SENDGRID_API_KEY)
|
||
|
|
default_from = current_app.config.get(self.SENDGRID_FROM_EMAIL)
|
||
|
|
fallback_from = current_app.config.get(self.FROM_ADDRESS)
|
||
|
|
|
||
|
|
if not api_key:
|
||
|
|
logger.error(
|
||
|
|
f"[SENDGRID] Cannot send — missing SENDGRID_API_KEY config. "
|
||
|
|
f"Would have sent to: {message.to} | Subject: {message.subject}"
|
||
|
|
)
|
||
|
|
return False
|
||
|
|
|
||
|
|
from_address = message.from_address or default_from or fallback_from
|
||
|
|
if not from_address:
|
||
|
|
logger.error(
|
||
|
|
f"[SENDGRID] Cannot send — missing from address (SENDGRID_FROM_EMAIL or FROM_ADDRESS). "
|
||
|
|
f"Would have sent to: {message.to} | Subject: {message.subject}"
|
||
|
|
)
|
||
|
|
return False
|
||
|
|
|
||
|
|
payload = {
|
||
|
|
"personalizations": [
|
||
|
|
{
|
||
|
|
"to": [{"email": message.to}]
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"from": {"email": from_address},
|
||
|
|
"subject": message.subject,
|
||
|
|
"content": [
|
||
|
|
{
|
||
|
|
"type": "text/plain",
|
||
|
|
"value": message.body
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
if message.html_body:
|
||
|
|
payload["content"].append({
|
||
|
|
"type": "text/html",
|
||
|
|
"value": message.html_body
|
||
|
|
})
|
||
|
|
|
||
|
|
try:
|
||
|
|
response = requests.post(
|
||
|
|
self.API_URL,
|
||
|
|
json=payload,
|
||
|
|
headers={
|
||
|
|
"Authorization": f"Bearer {api_key}",
|
||
|
|
"Content-Type": "application/json"
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
if response.status_code == 202:
|
||
|
|
logger.info(f"[SENDGRID] Sent to {message.to} | Subject: {message.subject}")
|
||
|
|
return True
|
||
|
|
else:
|
||
|
|
logger.error(
|
||
|
|
f"[SENDGRID] Failed to send to {message.to}: "
|
||
|
|
f"status={response.status_code} body={response.text}"
|
||
|
|
)
|
||
|
|
return False
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"[SENDGRID] Exception while sending to {message.to}: {e}")
|
||
|
|
return False
|