Skip to main content

Actionable Notifications

Some notifications carry metadata that enables inline Accept/Decline actions directly from the notification UI, without navigating to a separate page.

How It Works

Actionable Notification Types

Notification TypeMetadata FieldFrontend Action
INVITATION_RECEIVEDmetadata.tokenacceptCollaborationInvitation(token) / declineCollaborationInvitation(token)
CONNECTION_REQUEST_RECEIVEDmetadata.requestIdacceptCompanyConnectionRequestById(requestId) / declineCompanyConnectionRequestById(requestId)

Backend: Passing Metadata

The createFromTemplate() method accepts an optional fourth parameter for metadata, resource tracking, and company scoping:

await this.notificationService.createFromTemplate(
userId, // recipient
NotificationType.INVITATION_RECEIVED, // type (determines template)
{ // template variables (for title/message rendering)
invitationType: InvitationType.COLLABORATOR,
companyName: 'Tech Corp',
roleName: 'Recruiter',
inviterName: 'John Doe',
invitationId: 'invite-123',
invitationLink: 'https://...',
personalMessage: '',
},
{ // options (stored on notification record)
companyId: 'company-123',
resourceType: 'COLLABORATION_INVITATION',
resourceId: 'invite-123',
metadata: {
token: 'abc123def456', // frontend reads this for accept/decline
},
},
);

Key Distinction

  • Template variables (3rd param) — Used to render {{variable}} placeholders in title/message templates. Not stored as-is.
  • Options (4th param) — Stored directly on the notification record. The metadata JSON field is exposed to the frontend via GraphQL.

Frontend: Reading Metadata

The frontend checks notification.metadata to determine if inline actions should render:

// notificationActions.ts
function getNotificationActionContext(notification) {
if (notification.type === 'CONNECTION_REQUEST_RECEIVED') {
const requestId = notification.metadata?.requestId;
if (!requestId) return null;
return { actionType: 'CONNECTION_REQUEST', requestId };
}

if (notification.type === 'INVITATION_RECEIVED') {
const token = notification.metadata?.token;
if (!token) return null;
return { actionType: 'COLLABORATION', token };
}

return null;
}

Buttons only render when:

  1. Notification status is UNREAD
  2. Notification type is actionable
  3. Required metadata field is present

Adding New Actionable Types

To make a new notification type actionable:

  1. Backend listener — Pass the required identifier in options.metadata when calling createFromTemplate() or createNotification()
  2. Frontend notificationActions.ts — Add a new case in getNotificationActionContext() that extracts the identifier from metadata
  3. Frontend useNotificationActions.ts — Add the accept/decline mutation calls for the new action type
  4. Frontend NotificationActions.tsx — No changes needed (generic component)

Event Payloads

invitation.received

{
invitationId: string;
invitedUserId: string;
invitedEmail: string;
invitedByUserId: string;
companyId: string;
roleName: string;
invitationLink: string; // full URL with token
token: string; // invitation token (used in metadata)
message?: string;
}

connection.request.received

{
requestId: string; // connection request ID (used in metadata)
requesterCompanyId: string;
targetCompanyId: string;
requestMessage?: string;
}

Security

Notification metadata is only accessible to the notification's owner (userId). All notification queries are scoped by the authenticated user's ID — there is no way to read another user's notification metadata.

The invitation token in metadata is the same token that would be embedded in the actionUrl link. Exposing it in metadata does not increase the attack surface since the user already has access to the same token via the notification's actionUrl field.