import logging
from django.conf import settings
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
logger = logging.getLogger(__name__)
[docs]
def send_verification_email(user):
"""
Send an email verification link to the given user via SendGrid.
Constructs an HTML email containing a verification link built from
``settings.FRONTEND_URL`` and the user's ``email_verification_token``.
The link routes to ``/verify-email?token=<token>`` on the frontend.
In **test mode** (``settings.SEND_EMAILS = False``) no email is sent;
the verification link is logged at ``INFO`` level instead and the
function returns ``True`` to allow the rest of the registration flow to
proceed unaffected.
In **production**, a SendGrid API call is made using
``settings.SENDGRID_API_KEY``. Any non-2xx response or raised exception
is caught, logged at ``ERROR`` level, and returned as ``False`` — callers
are responsible for deciding whether to surface this failure to the user.
:param user: The user to whom the verification email should be sent.
Must have a non-empty ``email_verification_token``; returns ``False``
immediately if the token is absent.
:type user: users.models.User
:returns: ``True`` if the email was sent successfully (or skipped in test
mode), ``False`` if the token is missing, SendGrid returns a non-2xx
status, or any exception is raised during the API call.
:rtype: bool
**Required settings:**
- ``SEND_EMAILS`` — ``False`` to suppress sending in test/dev environments.
- ``FRONTEND_URL`` — Base URL used to construct the verification link.
- ``DEFAULT_FROM_EMAIL`` — Sender address for all outbound mail.
- ``SENDGRID_API_KEY`` — API key for the SendGrid client.
Example::
>>> token = user.generate_verification_token()
>>> success = send_verification_email(user)
>>> if not success:
... logger.warning('Verification email failed; user must request resend.')
"""
if not user.email_verification_token:
return False
if not settings.SEND_EMAILS:
logger.info(f"TEST MODE: Would send verification email to {user.email}")
logger.info(f"Verification link: {settings.FRONTEND_URL}/verify-email?token={user.email_verification_token}")
return True
verification_link = f"{settings.FRONTEND_URL}/verify-email?token={user.email_verification_token}"
message = Mail(
from_email=settings.DEFAULT_FROM_EMAIL,
to_emails=user.email,
subject="Verify your BruinBridge account",
html_content=f"""
<p>Please verify your email address to activate your account.</p>
<p>Click the link below or copy and paste it into your browser:</p>
<a href="{verification_link}">{verification_link}</a>
<p>This link will expire in 1 hour.</p>
"""
)
try:
sg = SendGridAPIClient(settings.SENDGRID_API_KEY)
response = sg.send(message)
if 200 <= response.status_code < 300:
logger.info(f"Verification email sent to {user.email}")
return True
else:
logger.error(f"SendGrid returned status {response.status_code}")
return False
except Exception as e:
logger.error(f"Error sending verification email to {user.email}: {str(e)}")
return False
[docs]
def send_password_reset_email(user):
"""
Send a password reset link to the given user via SendGrid.
Constructs an HTML email containing a reset link built from
``settings.FRONTEND_URL`` and the user's ``password_reset_token``.
The link routes to ``/reset-password/<token>`` on the frontend.
In **test mode** (``settings.SEND_EMAILS = False``) no email is sent;
the reset link is logged at ``INFO`` level instead and the function
returns ``True`` to allow the password reset flow to proceed unaffected.
In **production**, a SendGrid API call is made using
``settings.SENDGRID_API_KEY``. Any non-2xx response or raised exception
is caught, logged at ``ERROR`` level, and returned as ``False`` — callers
are responsible for deciding whether to surface this failure to the user.
:param user: The user to whom the reset email should be sent.
Must have a non-empty ``password_reset_token``; returns ``False``
immediately if the token is absent.
:type user: users.models.User
:returns: ``True`` if the email was sent successfully (or skipped in test
mode), ``False`` if the token is missing, SendGrid returns a non-2xx
status, or any exception is raised during the API call.
:rtype: bool
**Required settings:**
- ``SEND_EMAILS`` — ``False`` to suppress sending in test/dev environments.
- ``FRONTEND_URL`` — Base URL used to construct the reset link.
- ``DEFAULT_FROM_EMAIL`` — Sender address for all outbound mail.
- ``SENDGRID_API_KEY`` — API key for the SendGrid client.
Example::
>>> token = user.generate_password_reset_token()
>>> success = send_password_reset_email(user)
>>> if not success:
... logger.warning('Reset email failed; user must request a new link.')
"""
if not user.password_reset_token:
return False
if not settings.SEND_EMAILS:
logger.info(f"TEST MODE: Would send password reset to {user.email}")
logger.info(f"Reset link: {settings.FRONTEND_URL}/reset-password/{user.password_reset_token}")
return True
reset_link = f"{settings.FRONTEND_URL}/reset-password/{user.password_reset_token}"
message = Mail(
from_email=settings.DEFAULT_FROM_EMAIL,
to_emails=user.email,
subject="Reset your BruinBridge password",
html_content=f"""
<p>You requested to reset your password.</p>
<p>To set a new password, click the link below or copy and paste it into your browser:</p>
<a href="{reset_link}">{reset_link}</a>
<p>This link will expire in 1 hour.</p>
"""
)
try:
sg = SendGridAPIClient(settings.SENDGRID_API_KEY)
response = sg.send(message)
if 200 <= response.status_code < 300:
logger.info(f"Password reset email sent to {user.email}")
return True
else:
logger.error(f"SendGrid returned status {response.status_code}")
return False
except Exception as e:
logger.error(f"Error sending password reset to {user.email}: {str(e)}")
return False