import logging from app.core.config import settings logger = logging.getLogger(__name__) class EmailService: """Best-effort email delivery via Resend. Never raises on failure.""" @staticmethod async def send_invite_email( to_email: str, code: str, plan: str, trial_days: int | None = None, signup_url: str = "https://resolutionflow.com/register", ) -> bool: if not settings.email_enabled: logger.warning("Email not sent — RESEND_API_KEY not configured") return False try: import resend resend.api_key = settings.RESEND_API_KEY plan_label = plan.capitalize() trial_text = f" with a {trial_days}-day free trial" if trial_days else "" subject = f"You're invited to ResolutionFlow ({plan_label} plan{trial_text})" html = _render_invite_html( code=code, plan_label=plan_label, trial_days=trial_days, signup_url=signup_url, ) resend.Emails.send( { "from": settings.FROM_EMAIL, "to": [to_email], "subject": subject, "html": html, } ) logger.info("Invite email sent to %s", to_email) return True except Exception: logger.exception("Failed to send invite email to %s", to_email) return False @staticmethod async def send_account_invite_email( to_email: str, code: str, account_name: str, role: str, signup_url: str = "https://resolutionflow.com/register", ) -> bool: if not settings.email_enabled: logger.warning("Email not sent — RESEND_API_KEY not configured") return False try: import resend resend.api_key = settings.RESEND_API_KEY role_label = role.capitalize() subject = f"You've been invited to join {account_name} on ResolutionFlow" html = _render_account_invite_html( code=code, account_name=account_name, role_label=role_label, signup_url=signup_url, ) resend.Emails.send( { "from": settings.FROM_EMAIL, "to": [to_email], "subject": subject, "html": html, } ) logger.info("Account invite email sent to %s", to_email) return True except Exception: logger.exception("Failed to send account invite email to %s", to_email) return False def _render_invite_html( code: str, plan_label: str, trial_days: int | None, signup_url: str, ) -> str: trial_section = "" if trial_days: trial_section = f"""

Your {trial_days}-day free trial starts when you register. After your trial ends, your account will revert to the Free plan.

""" return f"""
{trial_section}

ResolutionFlow

Decision Tree Platform for MSP Professionals

You've been invited to join ResolutionFlow on the {plan_label} plan.

Your Invite Code

{code}

Create Your Account

Enter the code above during registration, or click the button to get started.

""" def _render_account_invite_html( code: str, account_name: str, role_label: str, signup_url: str, ) -> str: return f"""

ResolutionFlow

Decision Tree Platform for MSP Professionals

You've been invited to join {account_name} as an {role_label}.

Your Invite Code

{code}

Create Your Account

Enter the code above during registration, or click the button to get started.

"""