Note: This release is in preview. Features described here don’t become generally available until the latest general availability date that Salesforce announces for this release. Before then, and where features are noted as beta, pilot, or developer preview, we can’t guarantee general availability within any particular time frame or at all. Make your purchase decisions only on the basis of generally available products and features.
NotificationActionHandler Interface
Implement this interface to execute an action on a custom notification.
Namespace
NotificationActionHandler Methods
The following are methods for NotificationActionHandler.
executeAction(actionableNotification)
Executes the actionable notification.
Signature
public Messaging.ActionResult executeAction(Messaging.ActionableNotification actionableNotification)
Parameters
-
actionableNotification:
Type: Messaging.ActionableNotification
An actionable custom notification.
Return Value
Type: Messaging.ActionResult
NotificationActionHandler Example Implementation
This is an example implementation of the Messaging.NotificationActionHandler interface.
1global class CaseNotificationActionHandler implements Messaging.NotificationActionHandler {
2
3 private static final String ACTION_REASSIGN_TO_QUEUE = 'reassignToQueue';
4 private static final String DEFAULT_QUEUE_NAME = 'Queue_Exec'; // Default queue name, can be customized
5
6 global Messaging.ActionResult executeAction(Messaging.ActionableNotification actionableNotification) {
7 try {
8 String actionIdentifier = actionableNotification.getActionIdentifier();
9 String targetId = actionableNotification.getTargetId();
10
11 if (String.isBlank(actionIdentifier) || String.isBlank(targetId)) {
12 return new Messaging.ActionResult.Builder()
13 .withSuccess(false)
14 .withMessage('Action identifier and target ID are required')
15 .withErrorCode(Messaging.ActionError.INVALID_ACTION_PARAMETERS)
16 .build();
17 }
18
19 // Validate that targetId is a valid Case ID
20 if (!targetId.startsWith('500')) {
21 return new Messaging.ActionResult.Builder()
22 .withSuccess(false)
23 .withMessage('Target ID must be a valid Case ID')
24 .withErrorCode(Messaging.ActionError.INVALID_ACTION_PARAMETERS)
25 .build();
26 }
27
28 switch on actionIdentifier {
29 when 'reassignToQueue' {
30 return reassignCaseToQueue(targetId);
31 }
32 when else {
33 return new Messaging.ActionResult.Builder()
34 .withSuccess(false)
35 .withMessage('Unsupported action identifier: ' + actionIdentifier)
36 .withErrorCode(Messaging.ActionError.ACTION_NOT_IMPLEMENTED)
37 .build();
38 }
39 }
40 } catch (Exception e) {
41 return new Messaging.ActionResult.Builder()
42 .withSuccess(false)
43 .withMessage('An unexpected error occurred: ' + e.getMessage())
44 .withErrorCode(Messaging.ActionError.INTERNAL_ERROR)
45 .build();
46 }
47 }
48
49 private Messaging.ActionResult reassignCaseToQueue(String caseId) {
50 try {
51 // Query the case to ensure it exists
52 List<Case> cases = [SELECT Id, CaseNumber, OwnerId FROM Case WHERE Id = :caseId LIMIT 1];
53
54 if (cases.isEmpty()) {
55 return new Messaging.ActionResult.Builder()
56 .withSuccess(false)
57 .withMessage('Case not found with ID: ' + caseId)
58 .withErrorCode(Messaging.ActionError.INVALID_ACTION_PARAMETERS)
59 .build();
60 }
61
62 Case caseToUpdate = cases[0];
63
64 // Query for the queue to assign the case to
65 List<Group> queues = [SELECT Id, Name FROM Group WHERE Type = 'Queue' AND DeveloperName = :DEFAULT_QUEUE_NAME LIMIT 1];
66
67 if (queues.isEmpty()) {
68 return new Messaging.ActionResult.Builder()
69 .withSuccess(false)
70 .withMessage('Queue not found: ' + DEFAULT_QUEUE_NAME)
71 .withErrorCode(Messaging.ActionError.INVALID_STATE)
72 .build();
73 }
74
75 // Assign the case to the queue
76 caseToUpdate.OwnerId = queues[0].Id;
77 update caseToUpdate;
78
79 return new Messaging.ActionResult.Builder()
80 .withSuccess(true)
81 .withMessage('Case ' + caseToUpdate.CaseNumber + ' successfully assigned to queue: ' + DEFAULT_QUEUE_NAME)
82 .build();
83
84 } catch (DmlException e) {
85 return new Messaging.ActionResult.Builder()
86 .withSuccess(false)
87 .withMessage('Failed to update case: ' + e.getMessage())
88 .withErrorCode(Messaging.ActionError.ACCESS_DENIED)
89 .build();
90 } catch (Exception e) {
91 return new Messaging.ActionResult.Builder()
92 .withSuccess(false)
93 .withMessage('Error reassigning case to queue: ' + e.getMessage())
94 .withErrorCode(Messaging.ActionError.INTERNAL_ERROR)
95 .build();
96 }
97 }
98}The following example tests the implementation:
1@IsTest
2global class TestNotificationActionHandler {
3 @IsTest
4 static void testActionHandler() {
5
6 //Set up the data, for example creating a case
7 Case newCase = new Case(
8 Subject = 'Important Case',
9 Status = 'New',
10 Priority = 'High'
11 );
12 insert newCase;
13
14 //Set up Actionable Notification data
15 Messaging.ActionableNotification notification =
16 new Messaging.ActionableNotification.Builder()
17 .withNotificationTypeId('0MLXXXXXXXXXXXX4AC')
18 .withActionIdentifier('testAction')
19 .withRecipientId(UserInfo.getUserId())
20 .withSenderId(UserInfo.getUserId())
21 .withTargetId(newCase.Id)
22 .withTargetPageRef('/lightning/r/Case/' + newCase.Id + '/view')
23 .build();
24
25
26 Messaging.ActionResult result = Test.testNotificationActionHandler(new CaseNotificationActionHandler(), notification);
27
28 //Insert assert statements here to verify your action
29 }
30}