aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc/email.ts
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-11-23 00:54:38 +0000
committerGitHub <noreply@github.com>2025-11-23 00:54:38 +0000
commit5f0934acc0f7dde119be9f0a42a42742ec128377 (patch)
treef13bd90961eab0c694eed101db0eea96e0fc4725 /packages/trpc/email.ts
parentdaee8e7a4f764f188e1773a9def1542513bf66e1 (diff)
downloadkarakeep-5f0934acc0f7dde119be9f0a42a42742ec128377.tar.zst
feat: Add invitation approval for shared lists (#2152)
* feat: Add invitation approval system for collaborative lists - Add database schema changes to support pending invitations - Add status field (pending/accepted/declined) to listCollaborators - Add invitedAt and invitedEmail fields for tracking - Add index on status for efficient queries - Update List model with invitation workflow methods - Modify addCollaboratorByEmail to create pending invitations - Add acceptInvitation() for users to accept invites - Add declineInvitation() for users to decline invites - Add revokeInvitation() for owners to revoke pending invites - Add getPendingInvitations() to get user's pending invites - Implement privacy protection for pending invitations - Mask user names as "Pending User" until invitation is accepted - Only show email to list owner for pending invitations - Update getSharedWithUser to only include accepted collaborations - Ensures lists only appear after invitation is accepted * feat: Add tRPC procedures and email notifications for list invitations - Add new tRPC procedures for invitation workflow - acceptInvitation: Allow users to accept pending invitations - declineInvitation: Allow users to decline invitations - revokeInvitation: Allow owners to revoke pending invitations - getPendingInvitations: Get all pending invitations for current user - Update getCollaborators output schema - Add status, invitedAt fields to collaborator objects - Support privacy-masked user info for pending invitations - Add sendListInvitationEmail function - Email notification when user is invited to collaborate - Includes list name, inviter name, and link to view invitation - Gracefully handles missing SMTP configuration - Integrate email sending into invitation workflow - Send email when new invitation is created - Send email when declined invitation is renewed - Catch and log errors without failing the invitation * feat: Add UI for list invitation approval workflow - Update ManageCollaboratorsModal to support pending invitations - Show "Pending" badge for pending invitations - Add revoke button for owners to cancel pending invitations - Update success message to reflect invitation sent - Disable role change and remove buttons for pending invitations - Create PendingInvitationsCard component - Display all pending invitations for the current user - Show list name, description, inviter, and role - Provide Accept and Decline buttons - Auto-hide when no pending invitations exist - Add PendingInvitationsCard to lists page - Show at the top of the lists page - Only renders when user has pending invitations * fix: Add missing translation keys and fix TypeScript errors - Add translation keys for invitation system - lists.collaborators.invitation_sent - lists.collaborators.pending - lists.collaborators.revoke - lists.collaborators.invitation_revoked - lists.collaborators.failed_to_revoke - lists.invitations.* (all invitation-related keys) - Fix TypeScript errors in email sending - Handle optional user.name with fallback to 'A user' * wip * fixes * more fixes * fix revoke * more improvements * comment fix * fix email url * fix schemas * split pending invites into components * more fixes * test * test fixes --------- Co-authored-by: Claude <noreply@anthropic.com>
Diffstat (limited to 'packages/trpc/email.ts')
-rw-r--r--packages/trpc/email.ts63
1 files changed, 63 insertions, 0 deletions
diff --git a/packages/trpc/email.ts b/packages/trpc/email.ts
index edf8ec92..3c0b8b39 100644
--- a/packages/trpc/email.ts
+++ b/packages/trpc/email.ts
@@ -179,3 +179,66 @@ If you didn't request a password reset, please ignore this email. Your password
await transporter.sendMail(mailOptions);
}
+
+export async function sendListInvitationEmail(
+ email: string,
+ inviterName: string,
+ listName: string,
+ listId: string,
+) {
+ if (!serverConfig.email.smtp) {
+ // Silently fail if email is not configured
+ return;
+ }
+
+ const transporter = createTransport({
+ host: serverConfig.email.smtp.host,
+ port: serverConfig.email.smtp.port,
+ secure: serverConfig.email.smtp.secure,
+ auth:
+ serverConfig.email.smtp.user && serverConfig.email.smtp.password
+ ? {
+ user: serverConfig.email.smtp.user,
+ pass: serverConfig.email.smtp.password,
+ }
+ : undefined,
+ });
+
+ const inviteUrl = `${serverConfig.publicUrl}/dashboard/lists?pendingInvitation=${encodeURIComponent(listId)}`;
+
+ const mailOptions = {
+ from: serverConfig.email.smtp.from,
+ to: email,
+ subject: `${inviterName} invited you to collaborate on "${listName}"`,
+ html: `
+ <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
+ <h2>You've been invited to collaborate on a list!</h2>
+ <p>${inviterName} has invited you to collaborate on the list <strong>"${listName}"</strong> in Karakeep.</p>
+ <p>Click the link below to view and accept or decline the invitation:</p>
+ <p>
+ <a href="${inviteUrl}" style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">
+ View Invitation
+ </a>
+ </p>
+ <p>If the button doesn't work, you can copy and paste this link into your browser:</p>
+ <p><a href="${inviteUrl}">${inviteUrl}</a></p>
+ <p>You can accept or decline this invitation from your Karakeep dashboard.</p>
+ <p>If you weren't expecting this invitation, you can safely ignore this email or decline it in your dashboard.</p>
+ </div>
+ `,
+ text: `
+You've been invited to collaborate on a list!
+
+${inviterName} has invited you to collaborate on the list "${listName}" in Karakeep.
+
+View your invitation by visiting this link:
+${inviteUrl}
+
+You can accept or decline this invitation from your Karakeep dashboard.
+
+If you weren't expecting this invitation, you can safely ignore this email or decline it in your dashboard.
+ `,
+ };
+
+ await transporter.sendMail(mailOptions);
+}