feat: Phase 6, Device provisioning and deployment of updates on git-pull
This commit is contained in:
87
backend/utils/email.py
Normal file
87
backend/utils/email.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import logging
|
||||
import resend
|
||||
from config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_client() -> resend.Resend:
|
||||
return resend.Resend(api_key=settings.resend_api_key)
|
||||
|
||||
|
||||
def send_email(to: str, subject: str, html: str) -> None:
|
||||
"""Send a transactional email via Resend. Logs errors but does not raise."""
|
||||
try:
|
||||
client = _get_client()
|
||||
client.emails.send({
|
||||
"from": settings.email_from,
|
||||
"to": to,
|
||||
"subject": subject,
|
||||
"html": html,
|
||||
})
|
||||
logger.info("Email sent to %s — subject: %s", to, subject)
|
||||
except Exception as exc:
|
||||
logger.error("Failed to send email to %s: %s", to, exc)
|
||||
raise
|
||||
|
||||
|
||||
def send_device_assignment_invite(
|
||||
customer_email: str,
|
||||
serial_number: str,
|
||||
customer_name: str | None = None,
|
||||
) -> None:
|
||||
"""Notify a customer that a Vesper device has been assigned to them."""
|
||||
greeting = f"Hi {customer_name}," if customer_name else "Hello,"
|
||||
html = f"""
|
||||
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h2 style="color: #111827;">Your Vesper device is ready</h2>
|
||||
<p>{greeting}</p>
|
||||
<p>A Vesper bell automation device has been registered and assigned to you.</p>
|
||||
<p>
|
||||
<strong>Serial Number:</strong>
|
||||
<code style="background: #f3f4f6; padding: 2px 6px; border-radius: 4px; font-size: 14px;">{serial_number}</code>
|
||||
</p>
|
||||
<p>Open the Vesper app and enter this serial number to get started.</p>
|
||||
<hr style="border: none; border-top: 1px solid #e5e7eb; margin: 24px 0;" />
|
||||
<p style="color: #6b7280; font-size: 12px;">
|
||||
If you did not expect this email, please contact your system administrator.
|
||||
</p>
|
||||
</div>
|
||||
"""
|
||||
send_email(
|
||||
to=customer_email,
|
||||
subject=f"Your Vesper device is ready — {serial_number}",
|
||||
html=html,
|
||||
)
|
||||
|
||||
|
||||
def send_device_provisioned_alert(
|
||||
admin_email: str,
|
||||
serial_number: str,
|
||||
hw_type: str,
|
||||
) -> None:
|
||||
"""Internal alert sent to an admin when a device reaches provisioned status."""
|
||||
html = f"""
|
||||
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h2 style="color: #111827;">Device Provisioned</h2>
|
||||
<p>A Vesper device has successfully provisioned and is ready to ship.</p>
|
||||
<table style="border-collapse: collapse; width: 100%; margin-top: 16px;">
|
||||
<tr>
|
||||
<td style="padding: 6px 12px; font-weight: bold; background: #f9fafb;">Serial Number</td>
|
||||
<td style="padding: 6px 12px; font-family: monospace;">{serial_number}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 6px 12px; font-weight: bold; background: #f9fafb;">Board Type</td>
|
||||
<td style="padding: 6px 12px;">{hw_type.upper()}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="margin-top: 24px;">
|
||||
<a href="#" style="color: #2563eb;">View in Admin Console</a>
|
||||
</p>
|
||||
</div>
|
||||
"""
|
||||
send_email(
|
||||
to=admin_email,
|
||||
subject=f"[Vesper] Device provisioned — {serial_number}",
|
||||
html=html,
|
||||
)
|
||||
Reference in New Issue
Block a user