diff --git a/backend/alembic/versions/0006_email_from_name.py b/backend/alembic/versions/0006_email_from_name.py new file mode 100644 index 0000000..86bc8c9 --- /dev/null +++ b/backend/alembic/versions/0006_email_from_name.py @@ -0,0 +1,23 @@ +"""add from_name to email settings + +Revision ID: 0006_email_from_name +Revises: 0005_target_owners +Create Date: 2026-02-12 +""" + +from alembic import op +import sqlalchemy as sa + + +revision = "0006_email_from_name" +down_revision = "0005_target_owners" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column("email_notification_settings", sa.Column("from_name", sa.String(length=255), nullable=True)) + + +def downgrade() -> None: + op.drop_column("email_notification_settings", "from_name") diff --git a/backend/app/api/routes/admin_settings.py b/backend/app/api/routes/admin_settings.py index d51e9fc..158b768 100644 --- a/backend/app/api/routes/admin_settings.py +++ b/backend/app/api/routes/admin_settings.py @@ -1,4 +1,5 @@ from email.message import EmailMessage +from email.utils import formataddr import smtplib import ssl @@ -34,6 +35,7 @@ def _to_out(settings: EmailNotificationSettings) -> EmailSettingsOut: smtp_host=settings.smtp_host, smtp_port=settings.smtp_port, smtp_username=settings.smtp_username, + from_name=settings.from_name, from_email=settings.from_email, use_starttls=settings.use_starttls, use_ssl=settings.use_ssl, @@ -64,6 +66,7 @@ async def update_email_settings( settings.smtp_host = payload.smtp_host.strip() if payload.smtp_host else None settings.smtp_port = payload.smtp_port settings.smtp_username = payload.smtp_username.strip() if payload.smtp_username else None + settings.from_name = payload.from_name.strip() if payload.from_name else None settings.from_email = str(payload.from_email) if payload.from_email else None settings.use_starttls = payload.use_starttls settings.use_ssl = payload.use_ssl @@ -94,7 +97,7 @@ async def test_email_settings( password = decrypt_secret(settings.encrypted_smtp_password) if settings.encrypted_smtp_password else None message = EmailMessage() - message["From"] = settings.from_email + message["From"] = formataddr((settings.from_name, settings.from_email)) if settings.from_name else settings.from_email message["To"] = str(payload.recipient) message["Subject"] = payload.subject message.set_content(payload.message) diff --git a/backend/app/models/models.py b/backend/app/models/models.py index 962925e..4131338 100644 --- a/backend/app/models/models.py +++ b/backend/app/models/models.py @@ -130,6 +130,7 @@ class EmailNotificationSettings(Base): smtp_port: Mapped[int] = mapped_column(Integer, nullable=False, default=587) smtp_username: Mapped[str | None] = mapped_column(String(255), nullable=True) encrypted_smtp_password: Mapped[str | None] = mapped_column(Text, nullable=True) + from_name: Mapped[str | None] = mapped_column(String(255), nullable=True) from_email: Mapped[str | None] = mapped_column(String(255), nullable=True) use_starttls: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) use_ssl: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) diff --git a/backend/app/schemas/admin_settings.py b/backend/app/schemas/admin_settings.py index 8a8c734..7427294 100644 --- a/backend/app/schemas/admin_settings.py +++ b/backend/app/schemas/admin_settings.py @@ -8,6 +8,7 @@ class EmailSettingsOut(BaseModel): smtp_host: str | None smtp_port: int smtp_username: str | None + from_name: str | None from_email: EmailStr | None use_starttls: bool use_ssl: bool @@ -23,6 +24,7 @@ class EmailSettingsUpdate(BaseModel): smtp_username: str | None = None smtp_password: str | None = None clear_smtp_password: bool = False + from_name: str | None = None from_email: EmailStr | None = None use_starttls: bool = True use_ssl: bool = False diff --git a/backend/app/services/alert_notifications.py b/backend/app/services/alert_notifications.py index e0a0071..6280ec2 100644 --- a/backend/app/services/alert_notifications.py +++ b/backend/app/services/alert_notifications.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio from datetime import datetime, timedelta, timezone from email.message import EmailMessage +from email.utils import formataddr import smtplib import ssl @@ -21,6 +22,7 @@ async def _smtp_send( port: int, username: str | None, password: str | None, + from_name: str | None, from_email: str, recipient: str, subject: str, @@ -30,7 +32,7 @@ async def _smtp_send( ) -> None: def _send() -> None: message = EmailMessage() - message["From"] = from_email + message["From"] = formataddr((from_name, from_email)) if from_name else from_email message["To"] = recipient message["Subject"] = subject message.set_content(body) @@ -132,6 +134,7 @@ async def process_target_owner_notifications(db: AsyncSession, status: AlertStat port=settings.smtp_port, username=settings.smtp_username, password=password, + from_name=settings.from_name, from_email=settings.from_email, recipient=recipient, subject=subject, diff --git a/frontend/src/pages/AdminUsersPage.jsx b/frontend/src/pages/AdminUsersPage.jsx index 230c671..d4dda3e 100644 --- a/frontend/src/pages/AdminUsersPage.jsx +++ b/frontend/src/pages/AdminUsersPage.jsx @@ -13,6 +13,7 @@ export function AdminUsersPage() { smtp_username: "", smtp_password: "", clear_smtp_password: false, + from_name: "", from_email: "", use_starttls: true, use_ssl: false, @@ -37,6 +38,7 @@ export function AdminUsersPage() { smtp_username: smtp.smtp_username || "", smtp_password: "", clear_smtp_password: false, + from_name: smtp.from_name || "", from_email: smtp.from_email || "", use_starttls: !!smtp.use_starttls, use_ssl: !!smtp.use_ssl, @@ -85,6 +87,7 @@ export function AdminUsersPage() { ...emailSettings, smtp_host: emailSettings.smtp_host.trim() || null, smtp_username: emailSettings.smtp_username.trim() || null, + from_name: emailSettings.from_name.trim() || null, from_email: emailSettings.from_email.trim() || null, smtp_password: emailSettings.smtp_password || null, alert_recipients: recipients, @@ -249,6 +252,14 @@ export function AdminUsersPage() { /> +