You are on page 1of 15

2/26/24, 3:01 PM Push Notification - Overview

Push Notification
Last updated by | Millisande Bath | Dec 22, 2023 at 9:54 AM GMT

Introduction
We can differentiate 3 types of push notifications as shown in Figure 1.

Remote scheduled push notification


The marketing team can send a scheduled push notification.
For example, we can announce new charging stations to our customer (near their address).
Event-driven push notification
The sever can trigger a push notification to notify a real-time event.
For example, the server can send a failure event in the middle of a charging session to the
customer.
Client app local push notification
The mobile app can locally trigger or schedule a push notification based on the data on the device.
For example, the mobile app can send a push notification to the user 10 min before the overstay
fee is incurred.

Only the first two use cases will send push notifications via the Salesforce Marketing Cloud.

Figure 1 Types of push notifications

Local Push Notification


Example of local push notification:

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 1/15
2/26/24, 3:01 PM Push Notification - Overview

We can use the React native push notification library to implement local push notifications (
https://github.com/zo0r/react-native-push-notification ).

We will run a technical spike:

Setup
Install the package
iOS - Follow the installation instructions for PushNotificationIOS
Android - Manually update the AndroidManifest.xml (as per installation instruction ) in order to use
Scheduled Notifications.
Mobile app
Schedule a local push notification.
Use can click at the link of the notification to launch the mobile app.

References:

iOS local push notification -


https://developer.apple.com/documentation/usernotifications/scheduling_a_notification_locally_from_y
our_app

Push Notification via Salesforce Marketing Cloud


The device push notification is managed by Salesforce Marketing Cloud (SFMC) - MobilePush. MobilePush
delivers the capability to send push notifications to the customers. It can also offer silent notification, in-app
messaging, Bluetooth Beacons and Geo-location.

The high-level design is shown in Figure 1. Salesforce IDP will provide data feed (e.g. user ID, contact ID) to
the Salesforce Marketing Cloud.

There are two types of push notification use cases:

1. Scheduled push notification. Scheduled messages are sent to a group of users for specific events. We
may want to notify the users (of specific postcode) about maintenance activities or faulty issues.
2. Real-time push notification. Apollo XI EV Experience Platform can send a real-time notification to the
user about charging events, promotion et.

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 2/15
2/26/24, 3:01 PM Push Notification - Overview

Figure 2 Salesforce Marketing Cloud Push Notification

Some examples of event-driven (real-time) push notification:

TBC

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 3/15
2/26/24, 3:01 PM Push Notification - Overview

Ability to pass ContactId/PersonAccountId into SFMC to identify records and avoid the creation of
duplicates.

Step 1 - Provision The App for Push Service


We need to register with the mobile OS vendor (such as Apple or Google) for push service:

IOS - Developer creates two iOS Apple Push Notification service SSL Certificates (One for sandbox and
one for production). These certificates establish a secure connection between Marketing Cloud, Apple
and your app.
Android - Developer has to retrieve the Legacy Server Key and Sender ID from Google Firebase.

Step 2 - Create the Marketing Cloud Connect App


A connected app is created in SFMC by uploading the push credentials. The APNS Certificate and the Google
Key will be uploaded on the SFMC.

Step 3 - Integration with the SFMC SDK


The SFMC offers a MobilePush Software Development Kit (SDK) for both Android and iOS.

Android: https://github.com/salesforce-marketingcloud/MarketingCloudSDK-Android
iOS: https://github.com/salesforce-marketingcloud/MarketingCloudSDK-iOS
reactNative: https://github.com/salesforce-marketingcloud/react-native-marketingcloudsdk

The SFMC will provide the following configurations parameters to be configured in the SDK:

Access Token
App ID
App Endpoint
Account MID

For more implementation details, please refer to Mobile Push Notifications Implementation Guide​. There is
also a Salesforce Marketing Cloud React Native plugin.

Enabling Permissions
iOS implementations also require an additional step and you will need to ensure your mobile app is
requesting users to allow push notifications, this does not happen automatically in iOS as it does in Android.
There will be a design decision on when to best ask for permission.

It is also important to note that the initial implementation will only cover the basic push notifications. To be
able to use Geo-location or Inbox messaging you will need to ensure your app is asking for users
permissions and that your SDK is updated to include the following functions;

1. Geo-location a) Android - enableGeofenceMessaging() b) iOS - MarketingCloudSDKConfigBuilder()


sfmc_setLocationEnabled()
2. Inbox Messaging a) Android - InboxMessageMethods b) iOS - InboxMessageMethods

Contact Key
When the SDK adds contacts to Marketing Cloud, it will assign a random unique ID for unidentified app
users. Unless you are asking your users to register when they download the app Marketing Cloud will create

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 4/15
2/26/24, 3:01 PM Push Notification - Overview

new/duplicate users. And even if you do ask your users to register, the SDK will not automatically set the
correct contact key.

To prevent this from happening you will need your developers to add

sfmc_setContactKey for iOS


setDelayRegistrationUntilContactKeyIsSet for Android.

These functions hold off adding contacts to Marketing Cloud until you have been able to set the contact key,
which you can do by returning the Contact Key when verifying a user’s login credentials. We believe that this
setting of the Contact key is done when a Pulse user accepts marketing consents. The contact key is not the
same as the salesforce or authentication id. It is stored in salesforce in the Salesforce Core database account
object as the field PersonContactId

How to access the Contact Key


When the user registers we send a request to the following endpoint to set their marketing preferences

${idpInstanceUrl}/services/data/v48.0/sobjects/User/${userId}

idp instance in our preprod service is https://bp-idp-pp.bpglobal.com for example

@Chay Carnell is able to look up their consents data with a request to two endpoints:

GET https://bp-idp-pp.bpglobal.com/services/apexrest/UserConsent

This provides a consents id which can be used in the following request:

GET https://id.bp.com/services/data/v44.0/sobjects/REIDP_User_Consent__c/ [consent ID]

This second request returned the following object for his user:

{
"attributes": {
"type": "REIDP_User_Consent__c",
"url": "/services/data/v44.0/sobjects/REIDP_User_Consent__c/a0L4K00000E08SiUAJ"
},
"Id": "a0L4K00000E08SiUAJ",
"OwnerId": "0054K000007ZoMVQA0",
"IsDeleted": false,
"Name": "UC108269526",
"CurrencyIsoCode": "USD",
"CreatedDate": "2023-07-24T06:48:13.000+0000",
"CreatedById": "0054K000007ZoMVQA0",
"LastModifiedDate": "2023-07-24T06:48:13.000+0000",
"LastModifiedById": "0054K000007ZoMVQA0",
"SystemModstamp": "2023-07-24T06:48:13.000+0000",
"Contact__c": "0034K00000mY8r7QAC",
"REIDP_App_Name__c": "chargemaster"
}

The Contact__c field is the Contact ID/key.

We are working on how we can retrieve the data using CIP authenticated endpoints.

The current version of the contact api does not include returning the Contact ID

Upload Push/System Token

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 5/15
2/26/24, 3:01 PM Push Notification - Overview

You will not be able to send a push notification if you have not acquired the system/push token. This token
is generated by the OS vendor when an app user agrees to enable push notifications and is checked at the
time of sending by either Google or Apple. We need to implement SFPushNotificationManager class
following the Mobile Push Notifications Implementation Guide​.

User Device/Mobile Mobile OS Vendor Salesforce Marketing Cloud CIP Mulesoft

Launch app

User registration/login process

/ieeo-identitymgmt-b2c/papi/${apiVersion}/identity/user

Return

/iecm-contactmgmt-b2c/papi/${apiVersion}/contact

Return (contactId)

Asking for push notification consent

App registers to receive push on user's device

Return push token

set Device Id

Return

set Contact Id (contactId)

Return

Upload push token

Return

Return

User Device/Mobile Mobile OS Vendor Salesforce Marketing Cloud CIP Mulesoft

Key front end code examples


Open MainApplication.java file (Path -
android/app/src/main/java/com/bp/bppulse/mobile/MainApplication.java)

You need to write the below code which is inside double star(**) in onCreate() method

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 6/15
2/26/24, 3:01 PM Push Notification - Overview

@Override
public void onCreate() {
super.onCreate();
// If you opted-in for the New Architecture, we enable the TurboModule system
ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
SoLoader.init(this, /* native exopackage */ false);

**SFMCSdk.configure((Context) this, SFMCSdkModuleConfig.build(builder -> {


builder.setPushModuleConfig(MarketingCloudConfig.builder()
.setApplicationId(BuildConfig.SFMP_APP_ID)
.setAccessToken(BuildConfig.SFMP_ACCESS_TOKEN)
.setSenderId(BuildConfig.FCM_SENDER_ID)
.setMarketingCloudServerUrl(BuildConfig.SFMP_APP_ENDPOINT)
.setNotificationCustomizationOptions(NotificationCustomizationOptions.create(R.mipmap.ic_launcher
.setAnalyticsEnabled(true)
.setDelayRegistrationUntilContactKeyIsSet(true) // default = false
.build(this));

return null;
}), initializationStatus -> {
Log.e("TAG", "STATUS " + initializationStatus);
if (initializationStatus.getStatus() == 1) {
Log.e("TAG", "STATUS SUCCESS");
}
return null;
});**
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());

To write the above code, you need to import the below packages -

import com.salesforce.marketingcloud.MarketingCloudConfig;
import com.salesforce.marketingcloud.notifications.NotificationCustomizationOptions;
import com.salesforce.marketingcloud.notifications.NotificationManager;
import com.salesforce.marketingcloud.notifications.NotificationMessage;
import com.salesforce.marketingcloud.sfmcsdk.SFMCSdk;
import com.salesforce.marketingcloud.sfmcsdk.SFMCSdkModuleConfig;
import android.util.Log;

In AndroidManifest.xml (Path - android/app/src/main/AndroidManifest.xml)we need to declare the


POST_NOTIFICATION permission

<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

In android/app/ build.gradle file you need apply -

apply plugin: "com.facebook.react"


apply plugin: 'com.google.gms.google-services'

Open the android/ build.gradle file and add the following under buildscript -> dependencies

classpath 'com.google.gms:google-services:4.3.15' // Include the Google Services plugin dependencies

go further down and add the below code under allprojects -> repositories -

maven {
//Add SalesForce Marketing Cloud SDK repository
url "https://salesforce-marketingcloud.github.io/MarketingCloudSDK-Android/repository"
}

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 7/15
2/26/24, 3:01 PM Push Notification - Overview

In the FE, either you can create a provider or handler, here I create a handler for testing purpose. I give the
handler name PushNotificationHandler.tsx (Path -
src/components/PushNotificationHandler/PushNotificationHandler.tsx)

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 8/15
2/26/24, 3:01 PM Push Notification - Overview

import { useState, useEffect } from 'react';


import { PermissionsAndroid, Platform } from 'react-native';

import MCReactModule from 'react-native-marketingcloudsdk';

const PushNotificationHandler = () => {


const isAndroid = Platform.OS === 'android';
const [isPushEnabled, setPushEnabled] = useState(false);
const [pushToken, setPushToken] = useState('');
const [contactKey, setContactKey] = useState('');
const [deviceId, setDeviceId] = useState('');
const [attributes, setAttributes] = useState({});

// request for push notification permission


const requestNotificationPermission = async () => {
try {
await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
);
} catch (err) {
console.warn('requestNotificationPermission error: ', err);
}
};

// To enable push notification and also set system token


const updatePushData = async () => {
let enabled = await MCReactModule.isPushEnabled();
let systemToken = await MCReactModule.getSystemToken();
setPushEnabled(enabled);
setPushToken(systemToken || '');
console.info('System Token: ' + systemToken);
console.log('is push enabled- ', enabled);
if (!enabled) {
MCReactModule.enablePush();
console.info('Push Enabled');
setPushEnabled(true);
}
};

useEffect(() => {
updatePushData();
if (isAndroid) {
requestNotificationPermission();
}
}, []);

// --- Contack key Registration ---


const updateContactKey = async (msg?: string) => {
return Promise.resolve()
.then(MCReactModule.getContactKey)
.then(val => {
setContactKey(val || '');
console.log('updated contact key-', val);
msg && console.info(msg);
return val;
});
};

useEffect(() => {
// Manually update a contact key,it should be during login, which should be similar in BE
MCReactModule.setContactKey('<GET & SET CONTACT KEY WHILE LOGIN>');
updateContactKey('Added contact key');
}, []);

useEffect(() => {
MCReactModule.getDeviceId().then(val => {
setDeviceId(val || '');
console.log('device id', val);
});
}, []);

//---Attribute---
const updateAttributes = async (msg?: string) => {

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 9/15
2/26/24, 3:01 PM Push Notification - Overview
return Promise.resolve()
.then(MCReactModule.getAttributes)
.then(val => {
setAttributes(val || {});
console.log(val);
console.info(msg);
return val;
});
};

useEffect(() => {
// set the attributes for personalisation
MCReactModule.setAttribute('bp_pulse_App_Language', '<LOCALISATION>');
MCReactModule.setAttribute('bp_pulse_App_Locale', '<LOCALISATION>');
MCReactModule.setAttribute('bp_pulse_Marketing_Optin', '1');
MCReactModule.setAttribute('Contact_Key', '<GET & SET CONTACT KEY WHILE LOGIN>');
MCReactModule.setAttribute('FirstName', '<NAME OF THE USER>');
updateAttributes('Attribute added');
}, []);

return null;
};

export default PushNotificationHandler;

Now, call this handler (inside double ** ) from App.tsx

.......
........
<OutageProvider>
<FavouritesProvider>
<AnalyticsMiddleware />
<LaunchHandler />
**<PushNotificationHandler />**
<App />
</FavouritesProvider>
</OutageProvider>
..........
.........

Note- "react-native": "0.68.5" POST_NOTIFICATION not present in node_modules, for that we need to create
patch-package-

In order to fix this I had to create a patch using patch-package along with postinstall and update my current
PermissionAndroid files to support in my case POST_NOTIFICATIONS given I'm using react-native 0.68.5. So I
went ahead and updated these 2 files as detailed below:

/node_modules/react-native/Libraries/PermissionsAndroid/NativePermissionsAndroid.js

...
| 'android.permission.POST_NOTIFICATIONS';
...

/node_modules/react-native/Libraries/PermissionsAndroid/PermissionsAndroid.js

...
POST_NOTIFICATIONS: 'android.permission.POST_NOTIFICATIONS', <- added this
...
POST_NOTIFICATIONS: string, <- added this
....

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 10/15
2/26/24, 3:01 PM Push Notification - Overview

After this change the PermissionsAndroid module from react-native started to work again as expected
without getting any permission is null error.

BTW also don't forget to include the respective permission on the AndroidManifest, in my case was: <uses-
permission android:name="android.permission.POST_NOTIFICATIONS" />

Finally be sure to set the compileSdkVersion and the targetSdkVersion to 33 for the permission prompt to
show up...

Key back end code examples


From the BE we need to trigger the event by using Lambda function. We create the Lambda here -

downstream-provider-
local/infrastructure/cloudinformation/stacks/services/unversioned/notificationsStack/lambda/pushNotification
sTrigger/index.ts

In the index.ts file, we write the below code to connect SFMC auth and rest API by using lambda function -

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 11/15
2/26/24, 3:01 PM Push Notification - Overview

import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import axios, { AxiosRequestConfig } from 'axios';

const response = (status: number, message: string) => {


return {
statusCode: status,
body: JSON.stringify({
status: status,
message: message,
}),
};
};

const SFMC__AUTH_CLIENT_ID = '<Auth_Client_id_here>';


const SFMC__AUTH_CLIENT_SECRET = ' <Auth_client_secret_here>';
const SFMC__AUTH_ACC_ID = '<SFMC_mid>';
const SFMC_GRANT_TYPE = 'client_credentials';
const SFMC_AUTHENTICATION_API = "https://<APP_REGISTRATION_ID>.auth.marketingcloudapis.com/v2/token";
const SFMC_EVENT_NOTIFICATION_API = "https://<APP_REGISTRATION_ID>.rest.marketingcloudapis.com/interaction/
const EVENT_DEFINITION_KEY_PAYMENT_FAILURE= "<EVENT_NAME>";
const SFMC_CONTACT_KEY= "<CONTACT_KEY_FROM_FE_OR_DURING_LOGIN>";
/**
* Call the get authentication token from SFMC
* @param grantType
* @param clientId
* @param clientSecret
* @param accountId
* @returns
*/
async function getAuthAccessToken(grantType: string, clientId: string,
clientSecret: string, accountId: string):
Promise<string | null> {
try {
const authResponse = await axios.post(SFMC_AUTHENTICATION_API, {
grant_type: grantType,
client_id: clientId,
client_secret: clientSecret,
account_id: accountId,
}, {
headers: {
'Content-Type': 'application/json',
},
});
console.log('authResponse - ', authResponse.data);
const { access_token } = authResponse.data;
return access_token;
} catch (error) {
console.error('Error fetching access token:', error);
return null;
}
}

/**
* CALL THE EVENT BASED SFMC API TO SEND PUSH NOTIFICATION
* @param accessToken
*/
async function callEventBasedRestApiSF(accessToken: string):
Promise<string> {
const restApiUrl = SFMC_EVENT_NOTIFICATION_API;
const requestBody = {
contactKey: SFMC_CONTACT_KEY,
EventDefinitionKey: EVENT_DEFINITION_KEY_PAYMENT_FAILURE,
Data: {
ContactKey: SFMC_CONTACT_KEY,
DateInserted: new Date().toISOString(),
},
};

const config: AxiosRequestConfig = {

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 12/15
2/26/24, 3:01 PM Push Notification - Overview
method: 'POST',
url: restApiUrl,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
},
data: requestBody,
};

try {
const response = await axios(config);
console.log('Event notification REST API Response:', response.data);
return "EVENT_SUCCESS";
} catch (error) {
console.error('Event notification error calling REST API:', error);
return "EVENT_ERROR";
}
}

const createPushMessage = async (): Promise<APIGatewayProxyResult> => {


try {
const accessToken = await getAuthAccessToken(SFMC_GRANT_TYPE, SFMC__AUTH_CLIENT_ID,
SFMC__AUTH_CLIENT_SECRET, SFMC__AUTH_ACC_ID);

if (accessToken) {
const responseMsg= await callEventBasedRestApiSF(accessToken);
return response(200, `event response', ${responseMsg}`);
} else {
return response(400, `auth response', ${accessToken}`);
}
} catch (error) {
console.error(`Error creating push message:', ${error}`);
return response(400, `Error creating event push message:', ${error}`);
}
}

interface IBody {
userId?: string;
messageId?: number;
logTraceId?: string;
}
export function parseJson(json: string): IBody {
try {
return JSON.parse(json);
} catch (e) {
console.error('Failed to parse json:', e);
return { userId: undefined, messageId: undefined, logTraceId: undefined };
}
}

export const handler = async (


event: APIGatewayProxyEvent,
context: Context,
): Promise<APIGatewayProxyResult> => {
const requestBody = parseJson(event.body || '');

const { userId, messageId, logTraceId } = requestBody;

// Check if userId and messageId are present


if (!userId || !messageId) {

);
🚨
console.error(
` logTraceId ${logTraceId} - Error: userId or messageId is missing:userId: ${userId}, messageId: ${

return response(400, 'Triggered without either userId or messageId');


}

console.info(
`logTraceId: ${logTraceId} - Received event with userId: ${userId}, messageId: ${messageId}`,
);
return createPushMessage();

};

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 13/15
2/26/24, 3:01 PM Push Notification - Overview

To run the above lambda locally, we need to change run-local.ts file event part -

event: {
body: "{\"userId\": \"userId\",\"messageId\": 1}"
},

the above code only for testing purpose and on that path we need to run yarn start

Furthermore, we also need to create one API to call it, for that we create one controller & route (Path -
downstream-provider-local/unversioned/controllers/controller.ts) -

const { runLocalNonKinesisLambda } = require('../../lambdaLocal');

// Set the lambda names


const qaTokensRequestLambda = 'qa_tokens';
const notificationsRequestLambda = 'push_notifications';

const qaTokensRequest = async (req: any, res: any) => {


const result = await runLocalNonKinesisLambda(
qaTokensRequestLambda,
req.body,
);
res.json({
status: result ? 200 : 400,
message: (result && result.message) || 'Error',
});
};

const notificationsRequest = async (req: any, res: any) => {


const result = await runLocalNonKinesisLambda(
notificationsRequestLambda,
req.body,
);
res.json({
status: result ? 200 : 400,
message: (result && result.message) || 'Error',
});
};

export default {
qaTokensRequest,
notificationsRequest,
};

Route (Path - downstream-provider-local/unversioned/routes/route.ts) -

const router = require('express').Router();


import lambdaController from '../controllers/controllers';

router.post('/qaTokens', lambdaController.qaTokensRequest);
router.post('/notifications', lambdaController.notificationsRequest);

module.exports = router;

Note - ignore the qaToken in both the cases.

At the end, we need to declare it in downstream-provider-local/index.ts -

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 14/15
2/26/24, 3:01 PM Push Notification - Overview

....
const userRoutes = require('./user-api/routes/routes');
const dataSapRoutes = require('./data-api/routes/sap');
const batchPendingDeletionDateRoutes = require('./wallet-api/routes/routes');
const unversionedRoutes = require('./unversioned/routes/routes'); <- add this
...

....
app.use('/data/sap/', dataSapRoutes);
app.use('/wallet', batchPendingDeletionDateRoutes);
app.use('/unversioned/', unversionedRoutes); <- add this
....

In downstream-provider-local/lambdaLocal.ts file add the lambda path -

....
batch_pending_deletion_delete: `${walletServicePath}/batchPendingDeletionDelete/index.ts`,
qa_tokens: `${unversionedInfrastructureLambdaPath}/qaTokens/lambdas/tokenProxyLambda/index.js`,
push_notifications: `${unversionedInfrastructureLambdaPath}/notificationsStack/lambdas/pushNotificationsT
......

URLs in Push Notification


If a call to action should be part of messaging, we need to implement the following to ensure users have a
seamless experience when clicking on our push notification links.

iOS - MarketingCloudSDKURLHandlingDelegate protocol


Android - NotificationCustomizationOptions

Step 4 - Send Real-Time Push Notification


The ContactId/PersonAccountId will be passed into SFMC to identify records and avoid the creation of
duplicates.

There will an API key for sending a real-time push notification.

Device Mobile OS Vendor Salesforce Marketing Cloud EV Experience platform

Push (message, contact ID, country)

Push (message, token)

Push (message)

Device Mobile OS Vendor Salesforce Marketing Cloud EV Experience platform

https://dev.azure.com/bp-digital/bp_pulse/_wiki/wikis/bp_pulse/104605/Push-Notification 15/15

You might also like