""" SendGrid email service for password authentication. Sends setup and password reset emails to external users. """ import logging from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import Email, Mail from .config import Config logger = logging.getLogger(__name__) def _get_sendgrid_client() -> SendGridAPIClient | None: """Get SendGrid client if API key is configured.""" if not Config.SENDGRID_API_KEY: logger.warning("SENDGRID_API_KEY not configured, emails will not be sent") return None return SendGridAPIClient(Config.SENDGRID_API_KEY) def send_setup_email(email: str, token: str, base_url: str) -> tuple[bool, str]: """Send account setup email with magic link. Args: email: Recipient email address token: Setup token for the magic link base_url: Base URL of the application (e.g., https://data.example.com) Returns: Tuple of (success, message) """ client = _get_sendgrid_client() if not client: return False, "Email service not configured" setup_url = f"{base_url}/auth/setup/{token}" instance_name = Config.INSTANCE_NAME subject = f"Set up your {instance_name} account" html_content = f"""

{instance_name}

Hello,

You've been granted access to the {instance_name} platform. Click the button below to set up your password and complete your account setup.

Set Up Your Account

This link expires in 24 hours.
If you didn't request access to this platform, you can safely ignore this email.

Or copy and paste this URL into your browser:

{setup_url}

""" plain_content = f""" {instance_name} - Account Setup Hello, You've been granted access to the {instance_name} platform. Visit the link below to set up your password and complete your account setup: {setup_url} This link expires in 24 hours. If you didn't request access to this platform, you can safely ignore this email. --- This is an automated message from {instance_name} platform. """ from_email = Email(Config.EMAIL_FROM_ADDRESS, Config.EMAIL_FROM_NAME) message = Mail( from_email=from_email, to_emails=email, subject=subject, html_content=html_content, plain_text_content=plain_content, ) try: response = client.send(message) if response.status_code in (200, 201, 202): logger.info(f"Setup email sent to {email}") return True, "Setup email sent successfully" logger.error(f"SendGrid error: status {response.status_code}") return False, f"Failed to send email (status {response.status_code})" except Exception as e: logger.exception(f"Failed to send setup email to {email}: {e}") return False, f"Failed to send email: {e}" def send_reset_email(email: str, token: str, base_url: str) -> tuple[bool, str]: """Send password reset email with magic link. Args: email: Recipient email address token: Reset token for the magic link base_url: Base URL of the application Returns: Tuple of (success, message) """ client = _get_sendgrid_client() if not client: return False, "Email service not configured" reset_url = f"{base_url}/auth/reset/{token}" instance_name = Config.INSTANCE_NAME subject = f"Reset your {instance_name} password" html_content = f"""

{instance_name}

Hello,

We received a request to reset your password for your {instance_name} account. Click the button below to set a new password.

Reset Password

This link expires in 1 hour.
If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.

Or copy and paste this URL into your browser:

{reset_url}

""" plain_content = f""" {instance_name} - Password Reset Hello, We received a request to reset your password for your {instance_name} account. Visit the link below to set a new password: {reset_url} This link expires in 1 hour. If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged. --- This is an automated message from {instance_name} platform. """ from_email = Email(Config.EMAIL_FROM_ADDRESS, Config.EMAIL_FROM_NAME) message = Mail( from_email=from_email, to_emails=email, subject=subject, html_content=html_content, plain_text_content=plain_content, ) try: response = client.send(message) if response.status_code in (200, 201, 202): logger.info(f"Reset email sent to {email}") return True, "Reset email sent successfully" logger.error(f"SendGrid error: status {response.status_code}") return False, f"Failed to send email (status {response.status_code})" except Exception as e: logger.exception(f"Failed to send reset email to {email}: {e}") return False, f"Failed to send email: {e}"