Send a notification by Lambda function with AWS Pinpoint

Viewed 6835

I have an iOS app with a backend build by MobileHub with API Gateway, Lambda, and DynamoDB. I noticed that the SNS function of MobileHub has been replaced by Pinpoint, and I would like to create a notification system using this new service.

When a user creates a new post through API Gateway, a lambda function will be triggered and I suppose I can send a notification to the subscribers by Pinpoint. But I cannot find any example or reference doc in the official website of Pinpoint.

Do you have any resource for this scenario or any idea? Thank you very much!

4 Answers

Depends what you mean by notification, I assume you would like to send a push notification to a particular user (Pinpoint endpoint).

Pinpoint stores each device associated with a user as an "endpoint", generally created by the AWS client side analytics library (e.g. amplify analytics).

The client

With the amplify analytics library, I call updateEndpoint so I can specify a userId that is available to Lambda, as well as the device token and remove optOut so user can receive the push notification:

  1. Address - The token generated from user accepting push notification permission (iOS)
  2. optOut - NONE so they can receive push notifications
  3. userId - unique id for user (Cognito's sub)

Lambda (node.js)

Now you can send a push notification, using the userId and the Pinpoint SDK.

Example:

const sendMessagesParams = {
    ApplicationId: process.env.PINPOINT_APP_ID,
    SendUsersMessageRequest: {
        Users: {
            [receiverUserId]: {}
        },
        MessageConfiguration: {
            APNSMessage: {
                Action: 'OPEN_APP',
                Title: 'Message received',
                SilentPush: false,
                Body: `You have a new message`
            },
            GCMMessage: {
                Action: 'OPEN_APP',
                Title: 'Message received',
                SilentPush: false,
                Body: `You have a new message`
            }
        }
    }
};

console.log('sendMessagesParams', JSON.stringify(sendMessagesParams));

pinpoint.sendUsersMessages(sendMessagesParams, (sendMessagesErr, sendMessagesData) => console.log('push sent')

For your particular scenario, I set up a DynamoDB stream and trigger a Lambda when a record changes within the table. You may need to add the IAM permissions manually once the lambda is created.

Sources

I was struggling a lot getting a lambda function working, so please see this answer as an addition to the answer from Dylan w.

Client

import PushNotification from '@aws-amplify/pushnotification';
import Analytics from '@aws-amplify/analytics';

PushNotification.onRegister((token) => {

    Analytics.updateEndpoint({
        address: token,
        channelType: 'APNS',
        optOut: 'NONE',
        // Customized userId
        userId: "e236e3ea-bas9-4eae-967e-0eb9bcaca26d" // Example
    })

});

Lambda function

'use strict';

const AWS = require('aws-sdk');

exports.handler = async (event, context) => {

  var pinpoint = new AWS.Pinpoint();

  const sendMessagesParams = {
    ApplicationId: <YOUR_APPLICATION_ID>, // Find it in Pinpoint->All projects
    SendUsersMessageRequest: {
        Users:{<USER_ID>:{}}, // The same userId as set on the client. This way you can "follow" people if they switch device
        MessageConfiguration:{
          APNSMessage:{
            Action:"OPEN_APP",
            Title:"Message received",
            Body:"You have a new message"
        }
      }
    }
  };

  return await new Promise( (resolve, reject) => {

    pinpoint.sendUsersMessages(sendMessagesParams, (sendMessagesErr, sendMessagesData) => {
      if(sendMessagesErr) reject(sendMessagesErr)
      if(sendMessagesData) resolve(sendMessagesData)
    });

  });

};

Note that the call to pinpoint is wrapped in a promise. Because pinpoint.sendUserMessages accepts a callback, the execution is continued (async nature of Node), which will shut down the lambda function and you will get no output from the callback function or receive a notification, without awaiting the function to finish.

Finally, I've got something that perfectly works. The answer is that you have to use "targetClient" to update endpoint inside "didRegisterForRemoteNotificationsWithDeviceToken" function.

let client = self.pinpoint!.targetingClient
let profile = client.currentEndpointProfile()
print("EndpointId = \(profile.endpointId)")
profile.user?.userId = <YOUR_CUSTOM_ID>
client.update(profile)

Client Side(XCODE)

Here is my [AppDelegate.swift] looks like: (Important part is inside "didRegisterForRemoteNotificationsWithDeviceToken" function)

import UserNotifications
import AWSPinpoint
class AppDelegate: UIResponder, UIApplicationDelegate {
    var pinpoint: AWSPinpoint?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // Instantiate Pinpoint
        let pinpointConfiguration = AWSPinpointConfiguration.defaultPinpointConfiguration(launchOptions: launchOptions)
        // Set debug mode to use APNS sandbox, make sure to toggle for your production app
        pinpointConfiguration.debug = true
        self.pinpoint = AWSPinpoint(configuration: pinpointConfiguration)

        // Present the user with a request to authorize push notifications
        self.registerForPushNotifications()

        return true
    }
    func registerForPushNotifications() {
        UNUserNotificationCenter.current()
            .requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
                print("Permission granted: \(granted)")
                guard granted else { return }

                // Only get the notification settings if user has granted permissions
                self?.getNotificationSettings()
            }
    }
    func getNotificationSettings() {
        UNUserNotificationCenter.current().getNotificationSettings { settings in
            print("Notification settings: \(settings)")
            guard settings.authorizationStatus == .authorized else { return }

            DispatchQueue.main.async {
                // Register with Apple Push Notification service
                UIApplication.shared.registerForRemoteNotifications()
            }
        }
    }
    func application(_: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        print("DidRegisterForRemoteNotificationsWithDeviceToken: Start")
        let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
        let token = tokenParts.joined()
        print("Device Token: \(token)")
        // Register the device token with Pinpoint as the endpoint for this user
        self.pinpoint!.notificationManager.interceptDidRegisterForRemoteNotifications(withDeviceToken: deviceToken)

        //set custom userId and update endpoint
        let client = self.pinpoint!.targetingClient
        let profile = client.currentEndpointProfile()
        print("EndpointId = \(profile.endpointId)")
        profile.user?.userId = <YOUR_CUSTOM_ID>
        client.update(profile)
    }
    func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Failed to register: \(error)")
    }
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        print("didReceiveRemoteNotification:\(userInfo)")
        // Pass this remote notification event to pinpoint SDK to keep track of notifications produced by AWS Pinpoint campaigns.
        self.pinpoint!.notificationManager.interceptDidReceiveRemoteNotification(userInfo)
    }
}

Backend (Lambda with Nodejs)

And here is a code to send a notification to specific user.

'use strict';
const AWS = require('aws-sdk');
const pinpoint = new AWS.Pinpoint({region: 'us-west-2'});


exports.handler = (event, context, callback) => {
    
    const done = (err, data) => {
        if(err){
            console.log('ERROR:', err);
            const response = {
                statusCode: 400,
                body: JSON.stringify(err)
            };
            callback(response);
        }else{
            console.log('SUCCESS:', data);
            const response = {
                statusCode: 200,
                body: JSON.stringify(data)
            };
            callback(null, response);
        }
    };
    
    
    let users = {};
    users[<YOUR_CUSTOM_ID>] = {};
    const params = {
        ApplicationId: PINPOINT_PROJECT_ID, 
        SendUsersMessageRequest: {
            Users: users,
            MessageConfiguration: {
                APNSMessage: {
                    Action: 'OPEN_APP',
                    Title: "Hi, I am AWS Pinpoint.",
                    SilentPush: false,
                    Body: "You've got a nice message."
                }
            }
        }
    };
    pinpoint.sendUsersMessages(params, (err, data)=>{
        if(err){
            done(err);
        }else{
            done(null, data);
        }
    });
};

Hope those work for you too.

Related