Add platform SMTP and firm wizard invite email

This commit is contained in:
A R R R Associates
2026-07-02 23:43:24 +05:30
parent b4b8dc97fd
commit b84090d8c9
12 changed files with 821 additions and 45 deletions
+150 -1
View File
@@ -11,7 +11,7 @@ from typing import Any
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.modules.email_integration.models import EmailLog, EmailSetting, EmailTemplate
from app.modules.email_integration.models import EmailLog, EmailSetting, EmailTemplate, PlatformEmailSetting
from app.modules.email_integration.attachment_service import EmailAttachment
from app.modules.core.iam.models import User
from app.core.settings import get_settings
@@ -232,6 +232,38 @@ def seed_default_email_templates(db: Session, tenant_id: int | None = None, bran
existing_codes.add(code)
def get_platform_email_setting(db: Session) -> PlatformEmailSetting | None:
return db.execute(
select(PlatformEmailSetting).order_by(PlatformEmailSetting.id.asc())
).scalars().first()
def get_or_create_platform_email_setting(db: Session, actor_user_id: int | None = None) -> PlatformEmailSetting:
row = get_platform_email_setting(db)
if row:
return row
settings = get_settings()
row = PlatformEmailSetting(
smtp_host=getattr(settings, "SMTP_HOST", None) or "mail.vavalam.com",
smtp_port=int(getattr(settings, "SMTP_PORT", 465) or 465),
smtp_security="SSL",
smtp_username=getattr(settings, "SMTP_USERNAME", None) or "no-reply@vavalam.com",
smtp_password=getattr(settings, "SMTP_PASSWORD", None),
from_email=getattr(settings, "SMTP_FROM_EMAIL", None) or "no-reply@vavalam.com",
from_name=getattr(settings, "SMTP_FROM_NAME", None) or "ERP Platform",
reply_to_email=getattr(settings, "SMTP_FROM_EMAIL", None) or "no-reply@vavalam.com",
send_auth_emails=True,
is_active=True,
created_by_user_id=actor_user_id,
updated_by_user_id=actor_user_id,
)
db.add(row)
db.flush()
return row
def get_email_setting(db: Session, tenant_id: int | None, branch_id: int | None) -> EmailSetting | None:
if tenant_id is None:
return None
@@ -585,6 +617,123 @@ def process_pending_email_queue(
db.flush()
return result
def send_platform_email_log_now(db: Session, log: EmailLog) -> EmailLog:
"""Attempt immediate send through System Admin platform SMTP."""
setting = get_platform_email_setting(db)
now = datetime.now(timezone.utc)
log.processing_started_at = now
log.last_attempt_at = now
log.attempt_count = int(getattr(log, "attempt_count", 0) or 0) + 1
if not setting or not setting.is_active:
log.status = "SKIPPED"
log.error_message = "Platform SMTP settings not configured or inactive."
log.is_retryable = False
log.processing_started_at = None
db.flush()
return log
try:
_send_smtp(setting, log.recipient_email, log.subject, log.body or "", is_html=False)
log.status = "SENT"
log.sent_at = datetime.now(timezone.utc)
log.error_message = None
log.next_retry_at = None
log.processing_started_at = None
log.is_retryable = False
except Exception as exc:
log.status = "FAILED"
log.error_message = str(exc)
log.processing_started_at = None
log.is_retryable = False
log.next_retry_at = None
db.flush()
return log
def send_platform_template_email(
db: Session,
*,
recipient_email: str,
template_code: str,
context: dict[str, Any] | None = None,
related_module: str | None = None,
related_id: int | None = None,
send_immediately: bool = True,
queue_priority: int = 10,
) -> EmailLog:
"""Queue/send a platform email without tenant SMTP dependency."""
context = dict(context or {})
context.setdefault("firm_name", "ERP Platform")
context.setdefault("support_email", "")
template = _get_template(db, None, None, template_code)
if not template:
subject = template_code
body = ""
else:
subject = render_template_text(template.subject_template, context)
body = render_template_text(template.body_template, context)
log = EmailLog(
tenant_id=None,
branch_id=None,
recipient_email=recipient_email,
subject=subject[:500],
body=body,
status="PENDING",
related_module=related_module,
related_id=related_id,
template_code=template_code,
queued_at=datetime.now(timezone.utc),
max_attempts=1,
queue_priority=int(queue_priority or 10),
is_retryable=False,
)
db.add(log)
db.flush()
setting = get_platform_email_setting(db)
if not setting or not setting.is_active:
log.status = "SKIPPED"
log.error_message = "Platform SMTP settings not configured or inactive."
db.flush()
return log
if template_code.upper().startswith("AUTH_") and not getattr(setting, "send_auth_emails", True):
log.status = "SKIPPED"
log.error_message = "Platform authentication/invite emails are disabled."
db.flush()
return log
if send_immediately:
send_platform_email_log_now(db, log)
return log
def send_platform_user_invite_email(db: Session, *, user: User, invite_token: str, firm_name: str | None = None) -> EmailLog | None:
if not getattr(user, "email", None):
return None
invite_link = f"{_public_base_url()}/invite/accept?token={invite_token}"
return send_platform_template_email(
db,
recipient_email=str(user.email),
template_code="AUTH_USER_INVITE",
context={
"firm_name": firm_name or "ERP Platform",
"user_name": _user_display_name(user),
"user_email": str(user.email),
"invite_link": invite_link,
"expiry_hours": str(get_settings().INVITE_TOKEN_HOURS),
"support_email": "",
},
related_module="wizard_firm_invite",
related_id=int(user.id),
queue_priority=5,
)
def send_template_email(
db: Session,
*,