Files
thrillwiki_django_no_react/backend/apps/email_service/services.py
pacnpal d504d41de2 feat: complete monorepo structure with frontend and shared resources
- Add complete backend/ directory with full Django application
- Add frontend/ directory with Vite + TypeScript setup ready for Next.js
- Add comprehensive shared/ directory with:
  - Complete documentation and memory-bank archives
  - Media files and avatars (letters, park/ride images)
  - Deployment scripts and automation tools
  - Shared types and utilities
- Add architecture/ directory with migration guides
- Configure pnpm workspace for monorepo development
- Update .gitignore to exclude .django_tailwind_cli/ build artifacts
- Preserve all historical documentation in shared/docs/memory-bank/
- Set up proper structure for full-stack development with shared resources
2025-08-23 18:40:07 -04:00

113 lines
3.8 KiB
Python

import requests
from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import ImproperlyConfigured
from django.core.mail.message import sanitize_address
from .models import EmailConfiguration
import json
import base64
class EmailService:
@staticmethod
def send_email(
*,
to: str,
subject: str,
text: str,
from_email: str = None,
html: str = None,
reply_to: str = None,
request=None,
site=None,
):
# Get the site configuration
if site is None and request is not None:
site = get_current_site(request)
elif site is None:
raise ImproperlyConfigured("Either request or site must be provided")
try:
# Fetch the email configuration for the current site
email_config = EmailConfiguration.objects.get(site=site)
api_key = email_config.api_key
# Use provided from_email or construct from config
if not from_email:
from_email = f"{
email_config.from_name} <{
email_config.from_email}>"
elif "<" not in from_email:
# If from_email is provided but doesn't include a name, add the
# configured name
from_email = f"{email_config.from_name} <{from_email}>"
# Use provided reply_to or fall back to config
if not reply_to:
reply_to = email_config.reply_to
except EmailConfiguration.DoesNotExist:
raise ImproperlyConfigured(
f"Email configuration is missing for site: {site.domain}"
)
# Ensure the reply_to address is clean
reply_to = sanitize_address(reply_to, "utf-8")
# Format data for the API
data = {
"from": from_email, # Now includes the name in format "Name <email@domain.com>"
"to": to,
"subject": subject,
"text": text,
"replyTo": reply_to,
}
# Add HTML version if provided
if html:
data["html"] = html
# Debug output
print("\nEmail Service Debug:")
print(f"From: {from_email}")
print(f"To: {to}")
print(f"Reply-To: {reply_to}")
print(f"API Key: {api_key}")
print(f"Site: {site.domain}")
print(f"Request URL: {settings.FORWARD_EMAIL_BASE_URL}/v1/emails")
print(f"Request Data: {json.dumps(data, indent=2)}")
# Create Basic auth header with API key as username and empty password
auth_header = base64.b64encode(f"{api_key}:".encode()).decode()
headers = {
"Authorization": f"Basic {auth_header}",
"Accept": "application/json",
"Content-Type": "application/json",
}
try:
response = requests.post(
f"{settings.FORWARD_EMAIL_BASE_URL}/v1/emails",
json=data,
headers=headers,
timeout=60,
)
# Debug output
print(f"Response Status: {response.status_code}")
print(f"Response Headers: {dict(response.headers)}")
print(f"Response Body: {response.text}")
if response.status_code != 200:
error_message = response.text if response.text else "Unknown error"
raise Exception(
f"Failed to send email (Status {
response.status_code}): {error_message}"
)
return response.json()
except requests.RequestException as e:
raise Exception(f"Failed to send email: {str(e)}")
except Exception as e:
raise Exception(f"Failed to send email: {str(e)}")