Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
7 changes: 7 additions & 0 deletions django_email_learning/ports/email_sender_protocol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from typing import Protocol
from django.core.mail import EmailMultiAlternatives


class EmailSenderProtocol(Protocol):
def send_email(self, email: EmailMultiAlternatives) -> None:
...
Empty file.
Empty file.
33 changes: 33 additions & 0 deletions django_email_learning/services/deafults/email_sender.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import logging
from django_email_learning.ports.email_sender_protocol import EmailSenderProtocol
from django.core.mail import EmailMultiAlternatives

logger = logging.getLogger(__name__)


class DjangoEmailSender(EmailSenderProtocol):
def _mask_email(self, email_address: str) -> str:
"""Mask email address for logging privacy."""
try:
username, domain = email_address.split("@")
masked_username = username[0] + "***"
return f"{masked_username}@{domain}"
except ValueError:
return "***@***"

def _mask_recipients(self, recipients: list[str]) -> str:
"""Mask all recipient email addresses for logging."""
if not recipients:
return "no recipients"
masked = [self._mask_email(recipient) for recipient in recipients]
return ", ".join(masked)

def send_email(self, email: EmailMultiAlternatives) -> None:
masked_recipients = self._mask_recipients(email.to)
try:
logger.info(f"Sending email to {masked_recipients}")
email.send()
logger.info(f"Email sent successfully to {masked_recipients}")
except Exception as e:
logger.error(f"Failed to send email to {masked_recipients}: {str(e)}")
raise
Empty file added tests/services/__init__.py
Empty file.
Empty file.
40 changes: 40 additions & 0 deletions tests/services/defaults/test_email_sender.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from django_email_learning.services.deafults.email_sender import DjangoEmailSender
from django.core.mail import EmailMultiAlternatives
from unittest.mock import Mock
import pytest


@pytest.fixture
def email_sender() -> DjangoEmailSender:
return DjangoEmailSender()


@pytest.fixture
def email_multi_alternatives() -> EmailMultiAlternatives:
email = Mock(spec=EmailMultiAlternatives)
email.to = ["[email protected]"]
return email


def test_email_sender_logs_success_with_masked_recipients(
email_sender: DjangoEmailSender,
email_multi_alternatives: EmailMultiAlternatives,
caplog,
):
with caplog.at_level("INFO"):
email_sender.send_email(email_multi_alternatives)
assert "Sending email to r***@example.com" in caplog.text


def test_email_sender_logs_failure_with_masked_recipients(
email_sender: DjangoEmailSender,
email_multi_alternatives: EmailMultiAlternatives,
caplog,
):
email_multi_alternatives.send.side_effect = Exception("SMTP error")

with caplog.at_level("ERROR"):
with pytest.raises(Exception):
email_sender.send_email(email_multi_alternatives)

assert "Failed to send email to r***@example.com" in caplog.text