feat: Add email logging and monitoring system
- Add EmailLog model to database.py for tracking sent emails - Modify email_service.py to log all sent emails to database - Track email type (welcome, password_reset, notification) - Record sender, recipient, subject, status, timestamps - Supports monitoring email delivery success/failure Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
749abfa018
commit
a09203ef55
48
database.py
48
database.py
@ -2525,6 +2525,54 @@ class PopularPagesDaily(Base):
|
|||||||
return f"<PopularPagesDaily {self.date} {self.path}>"
|
return f"<PopularPagesDaily {self.date} {self.path}>"
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# EMAIL LOGGING
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
class EmailLog(Base):
|
||||||
|
"""
|
||||||
|
Log wszystkich wysłanych emaili systemowych.
|
||||||
|
|
||||||
|
Śledzi:
|
||||||
|
- Emaile rejestracyjne (weryfikacja)
|
||||||
|
- Emaile resetowania hasła
|
||||||
|
- Powiadomienia systemowe
|
||||||
|
- Status dostarczenia
|
||||||
|
|
||||||
|
Created: 2026-01-14
|
||||||
|
"""
|
||||||
|
__tablename__ = 'email_logs'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
|
||||||
|
# Dane emaila
|
||||||
|
email_type = Column(String(50), nullable=False, index=True) # welcome, password_reset, notification
|
||||||
|
recipient_email = Column(String(255), nullable=False, index=True)
|
||||||
|
recipient_name = Column(String(255), nullable=True)
|
||||||
|
subject = Column(String(500), nullable=False)
|
||||||
|
|
||||||
|
# Powiązanie z użytkownikiem (opcjonalne)
|
||||||
|
user_id = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
|
||||||
|
|
||||||
|
# Status
|
||||||
|
status = Column(String(20), default='pending', index=True) # pending, sent, failed
|
||||||
|
error_message = Column(Text, nullable=True)
|
||||||
|
|
||||||
|
# Metadane
|
||||||
|
sender_email = Column(String(255), nullable=True)
|
||||||
|
ip_address = Column(String(45), nullable=True) # IP requestu (jeśli dostępne)
|
||||||
|
|
||||||
|
# Timestamps
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
sent_at = Column(DateTime, nullable=True)
|
||||||
|
|
||||||
|
# Relacje
|
||||||
|
user = relationship('User', backref='email_logs')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<EmailLog {self.id} {self.email_type} -> {self.recipient_email} ({self.status})>"
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# DATABASE INITIALIZATION
|
# DATABASE INITIALIZATION
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@ -204,7 +204,10 @@ def send_email(
|
|||||||
subject: str,
|
subject: str,
|
||||||
body_text: str,
|
body_text: str,
|
||||||
body_html: Optional[str] = None,
|
body_html: Optional[str] = None,
|
||||||
from_address: Optional[str] = None
|
from_address: Optional[str] = None,
|
||||||
|
email_type: str = 'notification',
|
||||||
|
user_id: Optional[int] = None,
|
||||||
|
recipient_name: Optional[str] = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Send email using the global Email Service instance
|
Send email using the global Email Service instance
|
||||||
@ -215,6 +218,9 @@ def send_email(
|
|||||||
body_text: Plain text email body
|
body_text: Plain text email body
|
||||||
body_html: HTML email body (optional)
|
body_html: HTML email body (optional)
|
||||||
from_address: Sender email (optional)
|
from_address: Sender email (optional)
|
||||||
|
email_type: Type of email for logging (welcome, password_reset, notification)
|
||||||
|
user_id: User ID for logging (optional)
|
||||||
|
recipient_name: Recipient name for logging (optional)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if sent successfully, False otherwise
|
True if sent successfully, False otherwise
|
||||||
@ -227,7 +233,74 @@ def send_email(
|
|||||||
if isinstance(to, str):
|
if isinstance(to, str):
|
||||||
to = [to]
|
to = [to]
|
||||||
|
|
||||||
return _email_service.send_mail(to, subject, body_text, body_html, from_address)
|
result = _email_service.send_mail(to, subject, body_text, body_html, from_address)
|
||||||
|
|
||||||
|
# Log email to database
|
||||||
|
_log_email(
|
||||||
|
email_type=email_type,
|
||||||
|
recipient_emails=to,
|
||||||
|
recipient_name=recipient_name,
|
||||||
|
subject=subject,
|
||||||
|
user_id=user_id,
|
||||||
|
sender_email=from_address or _email_service.mail_from if _email_service else None,
|
||||||
|
success=result
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _log_email(
|
||||||
|
email_type: str,
|
||||||
|
recipient_emails: List[str],
|
||||||
|
subject: str,
|
||||||
|
success: bool,
|
||||||
|
recipient_name: Optional[str] = None,
|
||||||
|
user_id: Optional[int] = None,
|
||||||
|
sender_email: Optional[str] = None,
|
||||||
|
error_message: Optional[str] = None
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Log email to database for monitoring.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
email_type: Type of email (welcome, password_reset, notification)
|
||||||
|
recipient_emails: List of recipient email addresses
|
||||||
|
subject: Email subject
|
||||||
|
success: Whether email was sent successfully
|
||||||
|
recipient_name: Recipient name (optional)
|
||||||
|
user_id: User ID (optional)
|
||||||
|
sender_email: Sender email address (optional)
|
||||||
|
error_message: Error message if failed (optional)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from database import SessionLocal, EmailLog
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
for email in recipient_emails:
|
||||||
|
log_entry = EmailLog(
|
||||||
|
email_type=email_type,
|
||||||
|
recipient_email=email,
|
||||||
|
recipient_name=recipient_name,
|
||||||
|
subject=subject,
|
||||||
|
user_id=user_id,
|
||||||
|
sender_email=sender_email,
|
||||||
|
status='sent' if success else 'failed',
|
||||||
|
sent_at=datetime.utcnow() if success else None,
|
||||||
|
error_message=error_message if not success else None
|
||||||
|
)
|
||||||
|
db.add(log_entry)
|
||||||
|
db.commit()
|
||||||
|
logger.info(f"Email logged: {email_type} -> {recipient_emails} (status: {'sent' if success else 'failed'})")
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
logger.error(f"Failed to log email to database: {e}")
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
except ImportError:
|
||||||
|
# Database not available (e.g., during testing)
|
||||||
|
logger.warning("Could not log email - database module not available")
|
||||||
|
|
||||||
|
|
||||||
def is_configured() -> bool:
|
def is_configured() -> bool:
|
||||||
@ -325,7 +398,13 @@ https://nordabiznes.pl
|
|||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return send_email([email], subject, body_text, body_html)
|
return send_email(
|
||||||
|
to=[email],
|
||||||
|
subject=subject,
|
||||||
|
body_text=body_text,
|
||||||
|
body_html=body_html,
|
||||||
|
email_type='password_reset'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def send_welcome_email(email: str, name: str, verification_url: str) -> bool:
|
def send_welcome_email(email: str, name: str, verification_url: str) -> bool:
|
||||||
@ -417,4 +496,11 @@ https://nordabiznes.pl
|
|||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return send_email([email], subject, body_text, body_html)
|
return send_email(
|
||||||
|
to=[email],
|
||||||
|
subject=subject,
|
||||||
|
body_text=body_text,
|
||||||
|
body_html=body_html,
|
||||||
|
email_type='welcome',
|
||||||
|
recipient_name=name
|
||||||
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user