crowdata / app /utils /email_service.py
YOSOYYONOSOYOTRO's picture
Upload folder using huggingface_hub (part 2)
4223796 verified
Raw
History Blame Contribute Delete
4.31 kB
"""CrowData Email Service — Async email sending via SMTP."""
import logging
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
import aiosmtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from app.config import get_settings
logger = logging.getLogger(__name__)
settings = get_settings()
TEMPLATE_DIR = Path(__file__).parent.parent / "templates" / "emails"
jinja_env = Environment(loader=FileSystemLoader(str(TEMPLATE_DIR)))
async def send_email(
to_email: str,
subject: str,
template_name: str,
context: dict,
attachments: list[dict] | None = None,
):
"""
Send an HTML email using Jinja2 templates.
Args:
to_email: Recipient email address
subject: Email subject
template_name: Template file name (e.g., 'welcome.html')
context: Template context variables
attachments: Optional list of dicts with 'filename' and 'content' (bytes)
"""
try:
# Render template
template = jinja_env.get_template(template_name)
html_body = template.render(**context)
# Build message
msg = MIMEMultipart("alternative")
msg["From"] = f"{settings.from_name} <{settings.from_email}>"
msg["To"] = to_email
msg["Subject"] = subject
# Plain text fallback
plain_text = _html_to_plain(html_body)
msg.attach(MIMEText(plain_text, "plain", "utf-8"))
msg.attach(MIMEText(html_body, "html", "utf-8"))
# Attachments (e.g., PDF reports)
if attachments:
for att in attachments:
part = MIMEApplication(att["content"], Name=att["filename"])
part["Content-Disposition"] = f'attachment; filename="{att["filename"]}"'
msg.attach(part)
# Send
if not settings.smtp_user or not settings.smtp_password:
logger.warning(f"SMTP not configured — email to {to_email} NOT sent. Subject: {subject}")
return False
await aiosmtplib.send(
msg,
hostname=settings.smtp_host,
port=settings.smtp_port,
username=settings.smtp_user,
password=settings.smtp_password,
start_tls=settings.smtp_use_tls,
)
logger.info(f"Email sent to {to_email}: {subject}")
return True
except Exception as e:
logger.error(f"Failed to send email to {to_email}: {e}")
return False
async def send_welcome_email(to_email: str, full_name: str | None = None):
"""Send welcome email after registration."""
name = full_name or to_email.split("@")[0].title()
return await send_email(
to_email=to_email,
subject="Bienvenido a CrowData",
template_name="welcome.html",
context={
"name": name,
"email": to_email,
"login_url": "https://crowdata.ar/src/pages/login.html",
"dashboard_url": "https://crowdata.ar/src/pages/dashboard.html",
"support_email": settings.from_email,
},
)
async def send_pdf_report(to_email: str, report_type: str, identifier: str, pdf_bytes: bytes, user_name: str | None = None):
"""Send a PDF report as email attachment."""
type_labels = {
"persona": "Persona",
"empresa": "Empresa",
"vehiculo": "Vehículo",
"propiedad": "Inmueble",
}
type_label = type_labels.get(report_type, report_type)
filename = f"CrowData_{type_label}_{identifier}.pdf"
return await send_email(
to_email=to_email,
subject=f"CrowData — Informe de {type_label} {identifier}",
template_name="pdf_delivery.html",
context={
"name": user_name or to_email.split("@")[0].title(),
"report_type": type_label,
"identifier": identifier,
"support_email": settings.from_email,
},
attachments=[{"filename": filename, "content": pdf_bytes}],
)
def _html_to_plain(html: str) -> str:
"""Very basic HTML to plain text conversion."""
import re
text = re.sub(r"<br\s*/?>", "\n", html)
text = re.sub(r"<[^>]+>", "", text)
text = re.sub(r"\s+", " ", text).strip()
return text