mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 13:29:44 +02:00
feat(invitation): send email notification when inviting a user
Uses the existing Resend email service to notify invitees. Email includes inviter name, workspace name, and a link to the app. Sent fire-and-forget in a goroutine to avoid blocking the API response.
This commit is contained in:
@@ -134,11 +134,26 @@ func (h *Handler) CreateInvitation(w http.ResponseWriter, r *http.Request) {
|
||||
// Notify the invitee in real time if they are a registered user.
|
||||
userID := requestUserID(r)
|
||||
eventPayload := map[string]any{"invitation": resp}
|
||||
var workspaceName string
|
||||
if ws, err := h.Queries.GetWorkspace(r.Context(), parseUUID(workspaceID)); err == nil {
|
||||
workspaceName = ws.Name
|
||||
eventPayload["workspace_name"] = ws.Name
|
||||
}
|
||||
h.publish(protocol.EventInvitationCreated, workspaceID, "member", userID, eventPayload)
|
||||
|
||||
// Send invitation email (fire-and-forget).
|
||||
if h.EmailService != nil && workspaceName != "" {
|
||||
inviterName := email // fallback
|
||||
if inviter, err := h.Queries.GetUser(r.Context(), requester.UserID); err == nil {
|
||||
inviterName = inviter.Name
|
||||
}
|
||||
go func() {
|
||||
if err := h.EmailService.SendInvitationEmail(email, inviterName, workspaceName); err != nil {
|
||||
slog.Warn("failed to send invitation email", "email", email, "error", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, resp)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/resend/resend-go/v2"
|
||||
)
|
||||
@@ -52,3 +53,36 @@ func (s *EmailService) SendVerificationCode(to, code string) error {
|
||||
_, err := s.client.Emails.Send(params)
|
||||
return err
|
||||
}
|
||||
|
||||
// SendInvitationEmail notifies the invitee that they have been invited to a workspace.
|
||||
func (s *EmailService) SendInvitationEmail(to, inviterName, workspaceName string) error {
|
||||
// Build the app URL for the invitation — users will see pending invitations
|
||||
// in the workspace switcher after logging in.
|
||||
appURL := strings.TrimSpace(os.Getenv("FRONTEND_ORIGIN"))
|
||||
if appURL == "" {
|
||||
appURL = "https://app.multica.ai"
|
||||
}
|
||||
|
||||
if s.client == nil {
|
||||
fmt.Printf("[DEV] Invitation email to %s: %s invited you to %s — %s\n", to, inviterName, workspaceName, appURL)
|
||||
return nil
|
||||
}
|
||||
|
||||
params := &resend.SendEmailRequest{
|
||||
From: s.fromEmail,
|
||||
To: []string{to},
|
||||
Subject: fmt.Sprintf("%s invited you to %s on Multica", inviterName, workspaceName),
|
||||
Html: fmt.Sprintf(
|
||||
`<div style="font-family: sans-serif; max-width: 480px; margin: 0 auto;">
|
||||
<h2>You're invited to join %s</h2>
|
||||
<p><strong>%s</strong> invited you to collaborate in the <strong>%s</strong> workspace on Multica.</p>
|
||||
<p style="margin: 24px 0;">
|
||||
<a href="%s" style="display: inline-block; padding: 12px 24px; background: #000; color: #fff; text-decoration: none; border-radius: 6px; font-weight: 500;">Open Multica</a>
|
||||
</p>
|
||||
<p style="color: #666; font-size: 14px;">Log in to accept or decline the invitation.</p>
|
||||
</div>`, workspaceName, inviterName, workspaceName, appURL),
|
||||
}
|
||||
|
||||
_, err := s.client.Emails.Send(params)
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user