diff --git a/backend/app/services/email_templates.py b/backend/app/services/email_templates.py
new file mode 100644
index 0000000..f05896f
--- /dev/null
+++ b/backend/app/services/email_templates.py
@@ -0,0 +1,219 @@
+"""HTML and plain-text templates for transactional email."""
+
+from __future__ import annotations
+
+import html
+
+BRAND_NAME = "aGentic Email"
+BRAND_HANDLE = "@gentic.email"
+
+
+def _escape(value: str) -> str:
+ return html.escape(value, quote=True)
+
+
+def build_employee_invitation_email(
+ *,
+ member_first_name: str,
+ member_email: str,
+ manager_name: str,
+ manager_email: str,
+ company_domain: str,
+ login_url: str,
+ initial_password: str,
+ force_password_change: bool,
+) -> tuple[str, str, str]:
+ first_name = member_first_name.strip() or "there"
+ organization = company_domain.strip()
+ manager = manager_name.strip() or "Your administrator"
+ manager_address = manager_email.strip()
+
+ security_note = (
+ "For your security, you will be asked to create a new password "
+ "immediately after your first sign-in."
+ if force_password_change
+ else (
+ "We recommend updating your password after your first sign-in "
+ "from your account settings."
+ )
+ )
+
+ subject = (
+ f"Account invitation: {manager} added you to {organization} on {BRAND_NAME}"
+ )
+
+ unexpected_contact = (
+ f"If you were not expecting this invitation, please contact "
+ f"{manager} at {manager_address}."
+ if manager_address
+ else "If you were not expecting this invitation, please contact your administrator."
+ )
+
+ body_text = (
+ f"Hello {first_name},\n\n"
+ f"{manager} has created a work email account for you on {BRAND_NAME}, "
+ f"the email platform used by {organization}.\n\n"
+ f"This automated notification was sent to the personal email address "
+ f"your administrator provided. It contains the sign-in details for "
+ f"your new organization account.\n\n"
+ f"Organization: {organization}\n"
+ f"Work email address: {member_email}\n"
+ f"Sign-in page: {login_url}\n\n"
+ f"Sign-in credentials\n"
+ f" Email: {member_email}\n"
+ f" Initial password: {initial_password}\n\n"
+ f"{security_note}\n\n"
+ f"{unexpected_contact}\n\n"
+ f"—\n"
+ f"{BRAND_NAME}\n"
+ f"{login_url.rsplit('/login', 1)[0] or login_url}\n"
+ f"This is an account notification sent on behalf of {organization}."
+ )
+
+ preheader = (
+ f"{manager} added you to {organization}. "
+ f"Your work email account is ready — review your sign-in details."
+ )
+
+ safe_first = _escape(first_name)
+ safe_manager = _escape(manager)
+ safe_org = _escape(organization)
+ safe_email = _escape(member_email)
+ safe_password = _escape(initial_password)
+ safe_login = _escape(login_url)
+ safe_manager_email = _escape(manager_address)
+ safe_preheader = _escape(preheader)
+ safe_security = _escape(security_note)
+ safe_unexpected = _escape(unexpected_contact)
+
+ body_html = f"""
+
+
+
+
+
+
+ {_escape(subject)}
+
+
+
+ {safe_preheader}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ gentic.email
+
+
+
+
+
+
+
+
+
+
+ Organization invitation
+
+ You have been added to
+ {safe_org}
+
+
+
+
+
+
+ Hello {safe_first},
+
+
+ {safe_manager} has created a work email
+ account for you on {BRAND_NAME} ,
+ the email platform used by {safe_org}
+ for business communication.
+
+
+ This message was sent to the personal email address your administrator
+ provided. Use the details below to access your new organization inbox.
+
+
+
+
+ Account details
+
+
+ Organization
+ {safe_org}
+
+
+ Work email
+ {safe_email}
+
+
+ Initial password
+ {safe_password}
+
+
+
+
+
+
+
+ {safe_security}
+
+
+ {safe_unexpected}
+
+
+
+
+
+
+ {BRAND_NAME} · {BRAND_HANDLE}
+
+
+ This is an automated account notification sent on behalf of {safe_org}.
+ {" Reply to " + safe_manager_email + " if you need help." if manager_address else ""}
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+ return subject, body_text, body_html
diff --git a/backend/app/services/mail_sender.py b/backend/app/services/mail_sender.py
index b5f1716..16e44ff 100644
--- a/backend/app/services/mail_sender.py
+++ b/backend/app/services/mail_sender.py
@@ -19,12 +19,15 @@ def build_message(
subject: str,
body_text: str,
body_html: str | None,
+ reply_to: str | None = None,
) -> EmailMessage:
message = EmailMessage(policy=SMTP_POLICY)
message["From"] = sender
message["To"] = ", ".join(recipients)
if cc:
message["Cc"] = ", ".join(cc)
+ if reply_to:
+ message["Reply-To"] = reply_to
message["Subject"] = subject
message["Date"] = email.utils.formatdate(localtime=True)
message["Message-ID"] = email.utils.make_msgid(domain=sender.split("@")[-1])
@@ -90,6 +93,7 @@ async def deliver_email(
body_text: str,
body_html: str | None,
domain: MailDomain,
+ reply_to: str | None = None,
) -> None:
message = build_message(
sender=sender,
@@ -98,6 +102,7 @@ async def deliver_email(
subject=subject,
body_text=body_text,
body_html=body_html,
+ reply_to=reply_to,
)
signed = sign_message(message, domain)
await send_via_smtp(
diff --git a/backend/app/services/org_invitations.py b/backend/app/services/org_invitations.py
index 4a2cbab..a669ddd 100644
--- a/backend/app/services/org_invitations.py
+++ b/backend/app/services/org_invitations.py
@@ -5,8 +5,9 @@ from __future__ import annotations
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import get_settings
-from app.models import Account, MailDomain
+from app.models import Account
from app.services.domain_provisioning import get_domain_by_name
+from app.services.email_templates import BRAND_NAME, build_employee_invitation_email
from app.services.mail_sender import deliver_email
@@ -30,42 +31,26 @@ async def send_employee_invitation(
manager_name = f"{manager.first_name} {manager.last_name}".strip()
company = member.company_domain or settings.platform_domain
- change_note = (
- "You will be asked to choose a new password immediately after signing in."
- if force_password_change
- else "You can change your password later from account settings."
+ subject, body_text, body_html = build_employee_invitation_email(
+ member_first_name=member.first_name,
+ member_email=member.email,
+ manager_name=manager_name,
+ manager_email=manager.email,
+ company_domain=company,
+ login_url=login_url,
+ initial_password=initial_password,
+ force_password_change=force_password_change,
)
- body_text = (
- f"Hi {member.first_name},\n\n"
- f"{manager_name} invited you to AGMail for {company}.\n\n"
- f"Sign in at: {login_url}\n"
- f"Work email: {member.email}\n"
- f"Temporary password: {initial_password}\n\n"
- f"{change_note}\n\n"
- "— AGMail"
- )
- body_html = (
- f"Hi {member.first_name},
"
- f"{manager_name} invited you to AGMail for "
- f"{company} .
"
- f"Sign in to AGMail
"
- f""
- f"Work email: {member.email} "
- f"Temporary password: {initial_password} "
- f" "
- f"{change_note}
"
- f"— AGMail
"
- )
-
- sender = f"AGMail "
+ sender = f"{BRAND_NAME} "
await deliver_email(
sender=sender,
recipients=[member.invitation_email],
cc=[],
bcc=[],
- subject=f"You're invited to {company} on AGMail",
+ subject=subject,
body_text=body_text,
body_html=body_html,
domain=platform_domain,
+ reply_to=manager.email,
)
diff --git a/backend/tests/test_email_templates.py b/backend/tests/test_email_templates.py
new file mode 100644
index 0000000..96cd494
--- /dev/null
+++ b/backend/tests/test_email_templates.py
@@ -0,0 +1,32 @@
+from app.services.email_templates import build_employee_invitation_email
+
+
+def test_employee_invitation_uses_gentic_branding():
+ subject, body_text, body_html = build_employee_invitation_email(
+ member_first_name="Anurag",
+ member_email="anurag@road.rentals",
+ manager_name="Suyash Dixit",
+ manager_email="suyash@road.rentals",
+ company_domain="road.rentals",
+ login_url="https://gentic.email/login",
+ initial_password="Roadrentals123",
+ force_password_change=True,
+ )
+
+ assert "aGentic Email" in subject
+ assert subject == (
+ "Account invitation: Suyash Dixit added you to road.rentals on aGentic Email"
+ )
+ assert "AGMail" not in subject
+ assert "Suyash Dixit" in subject
+ assert "road.rentals" in subject
+
+ assert "Gentic Email" in body_text
+ assert "AGMail" not in body_text
+ assert "Suyash Dixit has created a work email account" in body_text
+ assert "anurag@road.rentals" in body_text
+
+ assert "gentic.email" in body_html
+ assert "Organization invitation" in body_html
+ assert "Sign in to aGentic Email" in body_html
+ assert "AGMail" not in body_html
diff --git a/frontend/src/components/AddEmployeeForm.jsx b/frontend/src/components/AddEmployeeForm.jsx
index 967c61c..599a585 100644
--- a/frontend/src/components/AddEmployeeForm.jsx
+++ b/frontend/src/components/AddEmployeeForm.jsx
@@ -242,7 +242,7 @@ export default function AddEmployeeForm({
Invitation email}
error={fieldErrors.invitation_email}
- hint={`Personal email where AGMail sends the setup invite from @${platformDomain}.`}
+ hint={`Personal email where aGentic Email sends the setup invite from @${platformDomain}.`}
>
Require password change on first login
- Employee must choose a new password before using AGMail.
+ Employee must choose a new password before using aGentic Email.
Choose a new password
Hi {account.first_name}, your administrator requires you to set a new password
- before using AGMail.
+ before using aGentic Email.