feat(organizations): email inviter when membership invite is accepted
When a user accepts an org invite, send a notification email to the person who sent the invite with membership details (member name, email, org name, role) and an optional View Organization button. Added build_invite_accepted_html() template to email_templates.py, wired it into the accept_invite() handler, and added a test case.
This commit is contained in:
@@ -243,6 +243,30 @@ def accept_invite(token):
|
|||||||
|
|
||||||
invite.accept()
|
invite.accept()
|
||||||
|
|
||||||
|
if invite.invited_by and invite.invited_by.email:
|
||||||
|
from gatehouse_app.services.email_templates import build_invite_accepted_html
|
||||||
|
from gatehouse_app.services.notification_service import NotificationService
|
||||||
|
|
||||||
|
member_display = user.full_name or user.email
|
||||||
|
inviter_display = invite.invited_by.full_name or invite.invited_by.email
|
||||||
|
org_link = f"{current_app.config.get('APP_URL', '')}/organizations/{invite.organization_id}"
|
||||||
|
|
||||||
|
html_body = build_invite_accepted_html(
|
||||||
|
inviter_name=inviter_display,
|
||||||
|
member_name=member_display,
|
||||||
|
member_email=user.email,
|
||||||
|
org_name=invite.organization.name,
|
||||||
|
role=invite.role,
|
||||||
|
org_link=org_link,
|
||||||
|
)
|
||||||
|
|
||||||
|
NotificationService._send_email_async(
|
||||||
|
to_address=invite.invited_by.email,
|
||||||
|
subject=f"{member_display} accepted your invitation to {invite.organization.name}",
|
||||||
|
body=f"{member_display} has accepted your invitation to join {invite.organization.name} on Secuird.",
|
||||||
|
html_body=html_body,
|
||||||
|
)
|
||||||
|
|
||||||
has_webauthn = user.has_webauthn_enabled()
|
has_webauthn = user.has_webauthn_enabled()
|
||||||
has_totp = user.has_totp_enabled()
|
has_totp = user.has_totp_enabled()
|
||||||
|
|
||||||
|
|||||||
@@ -562,3 +562,51 @@ def build_contact_enquiry_html(
|
|||||||
<p style="margin: 0; color: {TEXT_COLOR}; font-size: 14px; line-height: 1.6; white-space: pre-wrap;">{message_display}</p>
|
<p style="margin: 0; color: {TEXT_COLOR}; font-size: 14px; line-height: 1.6; white-space: pre-wrap;">{message_display}</p>
|
||||||
'''
|
'''
|
||||||
return get_base_html(content, f"Secuird Website: {type_label}", f"New {type_label} from {submitter_email}")
|
return get_base_html(content, f"Secuird Website: {type_label}", f"New {type_label} from {submitter_email}")
|
||||||
|
|
||||||
|
|
||||||
|
def build_invite_accepted_html(
|
||||||
|
inviter_name: str,
|
||||||
|
member_name: str,
|
||||||
|
member_email: str,
|
||||||
|
org_name: str,
|
||||||
|
role: str,
|
||||||
|
org_link: Optional[str] = None,
|
||||||
|
) -> str:
|
||||||
|
"""Build invite accepted notification email.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
inviter_name: Name of the person who sent the invite
|
||||||
|
member_name: Name of the person who accepted
|
||||||
|
member_email: Email of the person who accepted
|
||||||
|
org_name: Organization name
|
||||||
|
role: Role assigned to the member
|
||||||
|
org_link: Optional link to view the organization
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HTML email string
|
||||||
|
"""
|
||||||
|
content = f'''
|
||||||
|
<h2 style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 20px; font-weight: 600;">Invitation Accepted</h2>
|
||||||
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
||||||
|
<strong>{member_name}</strong> has accepted your invitation to join <strong>{org_name}</strong> on Secuird.
|
||||||
|
</p>
|
||||||
|
{get_alert_box(f"<strong>{member_name}</strong> ({member_email}) has joined <strong>{org_name}</strong>", "success", "✅")}
|
||||||
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="margin: 20px 0; background-color: {BACKGROUND_COLOR}; border-radius: 8px;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px;">
|
||||||
|
<h3 style="margin: 0 0 16px 0; color: {TEXT_COLOR}; font-size: 14px; font-weight: 600;">Membership Details</h3>
|
||||||
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0">
|
||||||
|
{get_detail_row("Member", member_name)}
|
||||||
|
{get_detail_row("Email", member_email)}
|
||||||
|
{get_detail_row("Organization", org_name)}
|
||||||
|
{get_detail_row("Role", role)}
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
'''
|
||||||
|
if org_link:
|
||||||
|
content += get_action_button(org_link, "View Organization", PRIMARY_COLOR)
|
||||||
|
|
||||||
|
return get_base_html(content, f"Invitation accepted: {org_name}", f"{member_name} has joined {org_name}")
|
||||||
|
|
||||||
|
|||||||
+21
-1
@@ -148,8 +148,28 @@ def test_html_email():
|
|||||||
success = provider.send(message)
|
success = provider.send(message)
|
||||||
print(f"Result: {'✅ SUCCESS' if success else '❌ FAILED'}")
|
print(f"Result: {'✅ SUCCESS' if success else '❌ FAILED'}")
|
||||||
|
|
||||||
|
# Test 8: Invite Accepted
|
||||||
|
print("\n--- Test 8: Invite Accepted ---")
|
||||||
|
html_body = email_templates.build_invite_accepted_html(
|
||||||
|
inviter_name="Admin User",
|
||||||
|
member_name="New Member",
|
||||||
|
member_email="newmember@example.com",
|
||||||
|
org_name="Acme Corporation",
|
||||||
|
role="Member",
|
||||||
|
org_link="https://secuird.tech/organizations/org-123",
|
||||||
|
)
|
||||||
|
message = EmailMessage(
|
||||||
|
to="cory@hawkvelt.id.au",
|
||||||
|
subject="New Member accepted your invitation to Acme Corporation",
|
||||||
|
body="Plain text version: New Member has accepted your invitation.",
|
||||||
|
html_body=html_body,
|
||||||
|
from_address="Secuird <noreply@secuird.tech>",
|
||||||
|
)
|
||||||
|
success = provider.send(message)
|
||||||
|
print(f"Result: {'✅ SUCCESS' if success else '❌ FAILED'}")
|
||||||
|
|
||||||
print("\n" + "=" * 50)
|
print("\n" + "=" * 50)
|
||||||
print("All 7 email templates sent!")
|
print("All 8 email templates sent!")
|
||||||
print("=" * 50)
|
print("=" * 50)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user