about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/core/providers/email
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/core/providers/email
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/core/providers/email')
-rw-r--r--.venv/lib/python3.12/site-packages/core/providers/email/__init__.py11
-rw-r--r--.venv/lib/python3.12/site-packages/core/providers/email/console_mock.py67
-rw-r--r--.venv/lib/python3.12/site-packages/core/providers/email/mailersend.py281
-rw-r--r--.venv/lib/python3.12/site-packages/core/providers/email/sendgrid.py257
-rw-r--r--.venv/lib/python3.12/site-packages/core/providers/email/smtp.py176
5 files changed, 792 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/core/providers/email/__init__.py b/.venv/lib/python3.12/site-packages/core/providers/email/__init__.py
new file mode 100644
index 00000000..38753695
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/core/providers/email/__init__.py
@@ -0,0 +1,11 @@
+from .console_mock import ConsoleMockEmailProvider
+from .mailersend import MailerSendEmailProvider
+from .sendgrid import SendGridEmailProvider
+from .smtp import AsyncSMTPEmailProvider
+
+__all__ = [
+    "ConsoleMockEmailProvider",
+    "AsyncSMTPEmailProvider",
+    "SendGridEmailProvider",
+    "MailerSendEmailProvider",
+]
diff --git a/.venv/lib/python3.12/site-packages/core/providers/email/console_mock.py b/.venv/lib/python3.12/site-packages/core/providers/email/console_mock.py
new file mode 100644
index 00000000..459a978d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/core/providers/email/console_mock.py
@@ -0,0 +1,67 @@
+import logging
+from typing import Optional
+
+from core.base import EmailProvider
+
+logger = logging.getLogger()
+
+
+class ConsoleMockEmailProvider(EmailProvider):
+    """A simple email provider that logs emails to console, useful for
+    testing."""
+
+    async def send_email(
+        self,
+        to_email: str,
+        subject: str,
+        body: str,
+        html_body: Optional[str] = None,
+        *args,
+        **kwargs,
+    ) -> None:
+        logger.info(f"""
+        -------- Email Message --------
+        To: {to_email}
+        Subject: {subject}
+        Body:
+        {body}
+        -----------------------------
+        """)
+
+    async def send_verification_email(
+        self, to_email: str, verification_code: str, *args, **kwargs
+    ) -> None:
+        logger.info(f"""
+        -------- Email Message --------
+        To: {to_email}
+        Subject: Please verify your email address
+        Body:
+        Verification code: {verification_code}
+        -----------------------------
+        """)
+
+    async def send_password_reset_email(
+        self, to_email: str, reset_token: str, *args, **kwargs
+    ) -> None:
+        logger.info(f"""
+        -------- Email Message --------
+        To: {to_email}
+        Subject: Password Reset Request
+        Body:
+        Reset token: {reset_token}
+        -----------------------------
+        """)
+
+    async def send_password_changed_email(
+        self, to_email: str, *args, **kwargs
+    ) -> None:
+        logger.info(f"""
+            -------- Email Message --------
+            To: {to_email}
+            Subject: Your Password Has Been Changed
+            Body:
+            Your password has been successfully changed.
+
+            For security reasons, you will need to log in again on all your devices.
+            -----------------------------
+            """)
diff --git a/.venv/lib/python3.12/site-packages/core/providers/email/mailersend.py b/.venv/lib/python3.12/site-packages/core/providers/email/mailersend.py
new file mode 100644
index 00000000..10fccd56
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/core/providers/email/mailersend.py
@@ -0,0 +1,281 @@
+import logging
+import os
+from typing import Optional
+
+from mailersend import emails
+
+from core.base import EmailConfig, EmailProvider
+
+logger = logging.getLogger(__name__)
+
+
+class MailerSendEmailProvider(EmailProvider):
+    """Email provider implementation using MailerSend API."""
+
+    def __init__(self, config: EmailConfig):
+        super().__init__(config)
+        self.api_key = config.mailersend_api_key or os.getenv(
+            "MAILERSEND_API_KEY"
+        )
+        if not self.api_key or not isinstance(self.api_key, str):
+            raise ValueError("A valid MailerSend API key is required.")
+
+        self.from_email = config.from_email or os.getenv("R2R_FROM_EMAIL")
+        if not self.from_email or not isinstance(self.from_email, str):
+            raise ValueError("A valid from email is required.")
+
+        self.frontend_url = config.frontend_url or os.getenv(
+            "R2R_FRONTEND_URL"
+        )
+        if not self.frontend_url or not isinstance(self.frontend_url, str):
+            raise ValueError("A valid frontend URL is required.")
+
+        self.verify_email_template_id = (
+            config.verify_email_template_id
+            or os.getenv("MAILERSEND_VERIFY_EMAIL_TEMPLATE_ID")
+        )
+        self.reset_password_template_id = (
+            config.reset_password_template_id
+            or os.getenv("MAILERSEND_RESET_PASSWORD_TEMPLATE_ID")
+        )
+        self.password_changed_template_id = (
+            config.password_changed_template_id
+            or os.getenv("MAILERSEND_PASSWORD_CHANGED_TEMPLATE_ID")
+        )
+        self.client = emails.NewEmail(self.api_key)
+        self.sender_name = config.sender_name or "R2R"
+
+        # Logo and documentation URLs
+        self.docs_base_url = f"{self.frontend_url}/documentation"
+
+    def _get_base_template_data(self, to_email: str) -> dict:
+        """Get base template data used across all email templates."""
+        return {
+            "user_email": to_email,
+            "docs_url": self.docs_base_url,
+            "quickstart_url": f"{self.docs_base_url}/quickstart",
+            "frontend_url": self.frontend_url,
+        }
+
+    async def send_email(
+        self,
+        to_email: str,
+        subject: Optional[str] = None,
+        body: Optional[str] = None,
+        html_body: Optional[str] = None,
+        template_id: Optional[str] = None,
+        dynamic_template_data: Optional[dict] = None,
+    ) -> None:
+        try:
+            logger.info("Preparing MailerSend message...")
+
+            mail_body = {
+                "from": {
+                    "email": self.from_email,
+                    "name": self.sender_name,
+                },
+                "to": [{"email": to_email}],
+            }
+
+            if template_id:
+                # Transform the template data to MailerSend's expected format
+                if dynamic_template_data:
+                    formatted_substitutions = {}
+                    for key, value in dynamic_template_data.items():
+                        formatted_substitutions[key] = {
+                            "var": key,
+                            "value": value,
+                        }
+                    mail_body["variables"] = [
+                        {
+                            "email": to_email,
+                            "substitutions": formatted_substitutions,
+                        }
+                    ]
+
+                mail_body["template_id"] = template_id
+            else:
+                mail_body.update(
+                    {
+                        "subject": subject or "",
+                        "text": body or "",
+                        "html": html_body or "",
+                    }
+                )
+
+            import asyncio
+
+            response = await asyncio.to_thread(self.client.send, mail_body)
+
+            # Handle different response formats
+            if isinstance(response, str):
+                # Clean the string response by stripping whitespace
+                response_clean = response.strip()
+                if response_clean in ["202", "200"]:
+                    logger.info(
+                        f"Email accepted for delivery with status code {response_clean}"
+                    )
+                    return
+            elif isinstance(response, int) and response in [200, 202]:
+                logger.info(
+                    f"Email accepted for delivery with status code {response}"
+                )
+                return
+            elif isinstance(response, dict) and response.get(
+                "status_code"
+            ) in [200, 202]:
+                logger.info(
+                    f"Email accepted for delivery with status code {response.get('status_code')}"
+                )
+                return
+
+            # If we get here, it's an error
+            error_msg = f"MailerSend error: {response}"
+            logger.error(error_msg)
+
+        except Exception as e:
+            error_msg = f"Failed to send email to {to_email}: {str(e)}"
+            logger.error(error_msg)
+
+    async def send_verification_email(
+        self,
+        to_email: str,
+        verification_code: str,
+        dynamic_template_data: Optional[dict] = None,
+    ) -> None:
+        try:
+            if self.verify_email_template_id:
+                verification_data = {
+                    "verification_link": f"{self.frontend_url}/verify-email?verification_code={verification_code}&email={to_email}",
+                    "verification_code": verification_code,  # Include code separately for flexible template usage
+                }
+
+                # Merge with any additional template data
+                template_data = {
+                    **(dynamic_template_data or {}),
+                    **verification_data,
+                }
+
+                await self.send_email(
+                    to_email=to_email,
+                    template_id=self.verify_email_template_id,
+                    dynamic_template_data=template_data,
+                )
+            else:
+                # Fallback to basic email if no template ID is configured
+                subject = "Verify Your R2R Account"
+                html_body = f"""
+                <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
+                    <h1>Welcome to R2R!</h1>
+                    <p>Please verify your email address to get started with R2R - the most advanced AI retrieval system.</p>
+                    <p>Click the link below to verify your email:</p>
+                    <p><a href="{self.frontend_url}/verify-email?verification_code={verification_code}&email={to_email}"
+                          style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">
+                        Verify Email
+                    </a></p>
+                    <p>Or enter this verification code: <strong>{verification_code}</strong></p>
+                    <p>If you didn't create an account with R2R, please ignore this email.</p>
+                </div>
+                """
+
+                await self.send_email(
+                    to_email=to_email,
+                    subject=subject,
+                    html_body=html_body,
+                    body=f"Welcome to R2R! Please verify your email using this code: {verification_code}",
+                )
+        except Exception as e:
+            error_msg = (
+                f"Failed to send verification email to {to_email}: {str(e)}"
+            )
+            logger.error(error_msg)
+
+    async def send_password_reset_email(
+        self,
+        to_email: str,
+        reset_token: str,
+        dynamic_template_data: Optional[dict] = None,
+    ) -> None:
+        try:
+            if self.reset_password_template_id:
+                reset_data = {
+                    "reset_link": f"{self.frontend_url}/reset-password?token={reset_token}",
+                    "reset_token": reset_token,
+                }
+
+                template_data = {**(dynamic_template_data or {}), **reset_data}
+
+                await self.send_email(
+                    to_email=to_email,
+                    template_id=self.reset_password_template_id,
+                    dynamic_template_data=template_data,
+                )
+            else:
+                subject = "Reset Your R2R Password"
+                html_body = f"""
+                <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
+                    <h1>Password Reset Request</h1>
+                    <p>You've requested to reset your R2R password.</p>
+                    <p>Click the link below to reset your password:</p>
+                    <p><a href="{self.frontend_url}/reset-password?token={reset_token}"
+                          style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">
+                        Reset Password
+                    </a></p>
+                    <p>Or use this reset token: <strong>{reset_token}</strong></p>
+                    <p>If you didn't request a password reset, please ignore this email.</p>
+                </div>
+                """
+
+                await self.send_email(
+                    to_email=to_email,
+                    subject=subject,
+                    html_body=html_body,
+                    body=f"Reset your R2R password using this token: {reset_token}",
+                )
+        except Exception as e:
+            error_msg = (
+                f"Failed to send password reset email to {to_email}: {str(e)}"
+            )
+            logger.error(error_msg)
+
+    async def send_password_changed_email(
+        self,
+        to_email: str,
+        dynamic_template_data: Optional[dict] = None,
+        *args,
+        **kwargs,
+    ) -> None:
+        try:
+            if (
+                hasattr(self, "password_changed_template_id")
+                and self.password_changed_template_id
+            ):
+                await self.send_email(
+                    to_email=to_email,
+                    template_id=self.password_changed_template_id,
+                    dynamic_template_data=dynamic_template_data,
+                )
+            else:
+                subject = "Your Password Has Been Changed"
+                body = """
+                Your password has been successfully changed.
+
+                If you did not make this change, please contact support immediately and secure your account.
+
+                """
+                html_body = """
+                <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
+                    <h1>Password Changed Successfully</h1>
+                    <p>Your password has been successfully changed.</p>
+                </div>
+                """
+                await self.send_email(
+                    to_email=to_email,
+                    subject=subject,
+                    html_body=html_body,
+                    body=body,
+                )
+        except Exception as e:
+            error_msg = f"Failed to send password change notification to {to_email}: {str(e)}"
+            logger.error(error_msg)
+            raise RuntimeError(error_msg) from e
diff --git a/.venv/lib/python3.12/site-packages/core/providers/email/sendgrid.py b/.venv/lib/python3.12/site-packages/core/providers/email/sendgrid.py
new file mode 100644
index 00000000..8b2553f1
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/core/providers/email/sendgrid.py
@@ -0,0 +1,257 @@
+import logging
+import os
+from typing import Optional
+
+from sendgrid import SendGridAPIClient
+from sendgrid.helpers.mail import Content, From, Mail
+
+from core.base import EmailConfig, EmailProvider
+
+logger = logging.getLogger(__name__)
+
+
+class SendGridEmailProvider(EmailProvider):
+    """Email provider implementation using SendGrid API."""
+
+    def __init__(self, config: EmailConfig):
+        super().__init__(config)
+        self.api_key = config.sendgrid_api_key or os.getenv("SENDGRID_API_KEY")
+        if not self.api_key or not isinstance(self.api_key, str):
+            raise ValueError("A valid SendGrid API key is required.")
+
+        self.from_email = config.from_email or os.getenv("R2R_FROM_EMAIL")
+        if not self.from_email or not isinstance(self.from_email, str):
+            raise ValueError("A valid from email is required.")
+
+        self.frontend_url = config.frontend_url or os.getenv(
+            "R2R_FRONTEND_URL"
+        )
+        if not self.frontend_url or not isinstance(self.frontend_url, str):
+            raise ValueError("A valid frontend URL is required.")
+
+        self.verify_email_template_id = (
+            config.verify_email_template_id
+            or os.getenv("SENDGRID_EMAIL_TEMPLATE_ID")
+        )
+        self.reset_password_template_id = (
+            config.reset_password_template_id
+            or os.getenv("SENDGRID_RESET_TEMPLATE_ID")
+        )
+        self.password_changed_template_id = (
+            config.password_changed_template_id
+            or os.getenv("SENDGRID_PASSWORD_CHANGED_TEMPLATE_ID")
+        )
+        self.client = SendGridAPIClient(api_key=self.api_key)
+        self.sender_name = config.sender_name
+
+        # Logo and documentation URLs
+        self.docs_base_url = f"{self.frontend_url}/documentation"
+
+    def _get_base_template_data(self, to_email: str) -> dict:
+        """Get base template data used across all email templates."""
+        return {
+            "user_email": to_email,
+            "docs_url": self.docs_base_url,
+            "quickstart_url": f"{self.docs_base_url}/quickstart",
+            "frontend_url": self.frontend_url,
+        }
+
+    async def send_email(
+        self,
+        to_email: str,
+        subject: Optional[str] = None,
+        body: Optional[str] = None,
+        html_body: Optional[str] = None,
+        template_id: Optional[str] = None,
+        dynamic_template_data: Optional[dict] = None,
+    ) -> None:
+        try:
+            logger.info("Preparing SendGrid message...")
+            message = Mail(
+                from_email=From(self.from_email, self.sender_name),
+                to_emails=to_email,
+            )
+
+            if template_id:
+                logger.info(f"Using dynamic template with ID: {template_id}")
+                message.template_id = template_id
+                base_data = self._get_base_template_data(to_email)
+                message.dynamic_template_data = {
+                    **base_data,
+                    **(dynamic_template_data or {}),
+                }
+            else:
+                if not subject:
+                    raise ValueError(
+                        "Subject is required when not using a template"
+                    )
+                message.subject = subject
+                message.add_content(Content("text/plain", body or ""))
+                if html_body:
+                    message.add_content(Content("text/html", html_body))
+
+            import asyncio
+
+            response = await asyncio.to_thread(self.client.send, message)
+
+            if response.status_code >= 400:
+                raise RuntimeError(
+                    f"Failed to send email: {response.status_code}"
+                )
+            elif response.status_code == 202:
+                logger.info("Message sent successfully!")
+            else:
+                error_msg = f"Failed to send email. Status code: {response.status_code}, Body: {response.body}"
+                logger.error(error_msg)
+                raise RuntimeError(error_msg)
+
+        except Exception as e:
+            error_msg = f"Failed to send email to {to_email}: {str(e)}"
+            logger.error(error_msg)
+            raise RuntimeError(error_msg) from e
+
+    async def send_verification_email(
+        self,
+        to_email: str,
+        verification_code: str,
+        dynamic_template_data: Optional[dict] = None,
+    ) -> None:
+        try:
+            if self.verify_email_template_id:
+                verification_data = {
+                    "verification_link": f"{self.frontend_url}/verify-email?verification_code={verification_code}&email={to_email}",
+                    "verification_code": verification_code,  # Include code separately for flexible template usage
+                }
+
+                # Merge with any additional template data
+                template_data = {
+                    **(dynamic_template_data or {}),
+                    **verification_data,
+                }
+
+                await self.send_email(
+                    to_email=to_email,
+                    template_id=self.verify_email_template_id,
+                    dynamic_template_data=template_data,
+                )
+            else:
+                # Fallback to basic email if no template ID is configured
+                subject = "Verify Your R2R Account"
+                html_body = f"""
+                <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
+                    <h1>Welcome to R2R!</h1>
+                    <p>Please verify your email address to get started with R2R - the most advanced AI retrieval system.</p>
+                    <p>Click the link below to verify your email:</p>
+                    <p><a href="{self.frontend_url}/verify-email?token={verification_code}&email={to_email}"
+                          style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">
+                        Verify Email
+                    </a></p>
+                    <p>Or enter this verification code: <strong>{verification_code}</strong></p>
+                    <p>If you didn't create an account with R2R, please ignore this email.</p>
+                </div>
+                """
+
+                await self.send_email(
+                    to_email=to_email,
+                    subject=subject,
+                    html_body=html_body,
+                    body=f"Welcome to R2R! Please verify your email using this code: {verification_code}",
+                )
+        except Exception as e:
+            error_msg = (
+                f"Failed to send verification email to {to_email}: {str(e)}"
+            )
+            logger.error(error_msg)
+            raise RuntimeError(error_msg) from e
+
+    async def send_password_reset_email(
+        self,
+        to_email: str,
+        reset_token: str,
+        dynamic_template_data: Optional[dict] = None,
+    ) -> None:
+        try:
+            if self.reset_password_template_id:
+                reset_data = {
+                    "reset_link": f"{self.frontend_url}/reset-password?token={reset_token}",
+                    "reset_token": reset_token,
+                }
+
+                template_data = {**(dynamic_template_data or {}), **reset_data}
+
+                await self.send_email(
+                    to_email=to_email,
+                    template_id=self.reset_password_template_id,
+                    dynamic_template_data=template_data,
+                )
+            else:
+                subject = "Reset Your R2R Password"
+                html_body = f"""
+                <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
+                    <h1>Password Reset Request</h1>
+                    <p>You've requested to reset your R2R password.</p>
+                    <p>Click the link below to reset your password:</p>
+                    <p><a href="{self.frontend_url}/reset-password?token={reset_token}"
+                          style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">
+                        Reset Password
+                    </a></p>
+                    <p>Or use this reset token: <strong>{reset_token}</strong></p>
+                    <p>If you didn't request a password reset, please ignore this email.</p>
+                </div>
+                """
+
+                await self.send_email(
+                    to_email=to_email,
+                    subject=subject,
+                    html_body=html_body,
+                    body=f"Reset your R2R password using this token: {reset_token}",
+                )
+        except Exception as e:
+            error_msg = (
+                f"Failed to send password reset email to {to_email}: {str(e)}"
+            )
+            logger.error(error_msg)
+            raise RuntimeError(error_msg) from e
+
+    async def send_password_changed_email(
+        self,
+        to_email: str,
+        dynamic_template_data: Optional[dict] = None,
+        *args,
+        **kwargs,
+    ) -> None:
+        try:
+            if (
+                hasattr(self, "password_changed_template_id")
+                and self.password_changed_template_id
+            ):
+                await self.send_email(
+                    to_email=to_email,
+                    template_id=self.password_changed_template_id,
+                    dynamic_template_data=dynamic_template_data,
+                )
+            else:
+                subject = "Your Password Has Been Changed"
+                body = """
+                Your password has been successfully changed.
+
+                If you did not make this change, please contact support immediately and secure your account.
+
+                """
+                html_body = """
+                <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
+                    <h1>Password Changed Successfully</h1>
+                    <p>Your password has been successfully changed.</p>
+                </div>
+                """
+                # Move send_email inside the else block
+                await self.send_email(
+                    to_email=to_email,
+                    subject=subject,
+                    html_body=html_body,
+                    body=body,
+                )
+        except Exception as e:
+            error_msg = f"Failed to send password change notification to {to_email}: {str(e)}"
+            logger.error(error_msg)
+            raise RuntimeError(error_msg) from e
diff --git a/.venv/lib/python3.12/site-packages/core/providers/email/smtp.py b/.venv/lib/python3.12/site-packages/core/providers/email/smtp.py
new file mode 100644
index 00000000..bd68ff36
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/core/providers/email/smtp.py
@@ -0,0 +1,176 @@
+import asyncio
+import logging
+import os
+import smtplib
+import ssl
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+from typing import Optional
+
+from core.base import EmailConfig, EmailProvider
+
+logger = logging.getLogger(__name__)
+
+
+class AsyncSMTPEmailProvider(EmailProvider):
+    """Email provider implementation using Brevo SMTP relay."""
+
+    def __init__(self, config: EmailConfig):
+        super().__init__(config)
+        self.smtp_server = config.smtp_server or os.getenv("R2R_SMTP_SERVER")
+        if not self.smtp_server:
+            raise ValueError("SMTP server is required")
+
+        self.smtp_port = config.smtp_port or os.getenv("R2R_SMTP_PORT")
+        if not self.smtp_port:
+            raise ValueError("SMTP port is required")
+
+        self.smtp_username = config.smtp_username or os.getenv(
+            "R2R_SMTP_USERNAME"
+        )
+        if not self.smtp_username:
+            raise ValueError("SMTP username is required")
+
+        self.smtp_password = config.smtp_password or os.getenv(
+            "R2R_SMTP_PASSWORD"
+        )
+        if not self.smtp_password:
+            raise ValueError("SMTP password is required")
+
+        self.from_email: Optional[str] = (
+            config.from_email
+            or os.getenv("R2R_FROM_EMAIL")
+            or self.smtp_username
+        )
+        self.ssl_context = ssl.create_default_context()
+
+    async def _send_email_sync(self, msg: MIMEMultipart) -> None:
+        """Synchronous email sending wrapped in asyncio executor."""
+        loop = asyncio.get_running_loop()
+
+        def _send():
+            with smtplib.SMTP_SSL(
+                self.smtp_server,
+                self.smtp_port,
+                context=self.ssl_context,
+                timeout=30,
+            ) as server:
+                logger.info("Connected to SMTP server")
+                server.login(self.smtp_username, self.smtp_password)
+                logger.info("Login successful")
+                server.send_message(msg)
+                logger.info("Message sent successfully!")
+
+        try:
+            await loop.run_in_executor(None, _send)
+        except Exception as e:
+            error_msg = f"Failed to send email: {str(e)}"
+            logger.error(error_msg)
+            raise RuntimeError(error_msg) from e
+
+    async def send_email(
+        self,
+        to_email: str,
+        subject: str,
+        body: str,
+        html_body: Optional[str] = None,
+        *args,
+        **kwargs,
+    ) -> None:
+        msg = MIMEMultipart("alternative")
+        msg["Subject"] = subject
+        msg["From"] = self.from_email  # type: ignore
+        msg["To"] = to_email
+
+        msg.attach(MIMEText(body, "plain"))
+        if html_body:
+            msg.attach(MIMEText(html_body, "html"))
+
+        try:
+            logger.info("Initializing SMTP connection...")
+            async with asyncio.timeout(30):  # Overall timeout
+                await self._send_email_sync(msg)
+        except asyncio.TimeoutError as e:
+            error_msg = "Operation timed out while trying to send email"
+            logger.error(error_msg)
+            raise RuntimeError(error_msg) from e
+        except Exception as e:
+            error_msg = f"Failed to send email: {str(e)}"
+            logger.error(error_msg)
+            raise RuntimeError(error_msg) from e
+
+    async def send_verification_email(
+        self, to_email: str, verification_code: str, *args, **kwargs
+    ) -> None:
+        body = f"""
+        Please verify your email address by entering the following code:
+
+        Verification code: {verification_code}
+
+        If you did not request this verification, please ignore this email.
+        """
+
+        html_body = f"""
+        <p>Please verify your email address by entering the following code:</p>
+        <p style="font-size: 24px; font-weight: bold; margin: 20px 0;">
+            Verification code: {verification_code}
+        </p>
+        <p>If you did not request this verification, please ignore this email.</p>
+        """
+
+        await self.send_email(
+            to_email=to_email,
+            subject="Please verify your email address",
+            body=body,
+            html_body=html_body,
+        )
+
+    async def send_password_reset_email(
+        self, to_email: str, reset_token: str, *args, **kwargs
+    ) -> None:
+        body = f"""
+        You have requested to reset your password.
+
+        Reset token: {reset_token}
+
+        If you did not request a password reset, please ignore this email.
+        """
+
+        html_body = f"""
+        <p>You have requested to reset your password.</p>
+        <p style="font-size: 24px; font-weight: bold; margin: 20px 0;">
+            Reset token: {reset_token}
+        </p>
+        <p>If you did not request a password reset, please ignore this email.</p>
+        """
+
+        await self.send_email(
+            to_email=to_email,
+            subject="Password Reset Request",
+            body=body,
+            html_body=html_body,
+        )
+
+    async def send_password_changed_email(
+        self, to_email: str, *args, **kwargs
+    ) -> None:
+        body = """
+        Your password has been successfully changed.
+
+        If you did not make this change, please contact support immediately and secure your account.
+
+        """
+
+        html_body = """
+        <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
+            <h1>Password Changed Successfully</h1>
+            <p>Your password has been successfully changed.</p>
+        </div>
+        """
+
+        await self.send_email(
+            to_email=to_email,
+            subject="Your Password Has Been Changed",
+            body=body,
+            html_body=html_body,
+        )