はじめに
flutterでpush通知の実装を目的に、Lambdaを経由してAmazon Pinpointでpush通知する構築方法についてまとめます。
- pinpointに対して、エンドポイントの登録
- pinpointに対して、エンドポイントの更新
- pinpointに対して、セグメントを作成
- pinpointに対して、セグメントとキャンペーンを作成
- pinpointに対して、セグメントとキャンペーンを更新
- pinpointに対して、セグメントとキャンペーンを削除
前提条件
- push通知は、iosとandroidを対象
用語の説明
用語の説明は、通知タイプの内push通知を意識した説明
にします。
エンドポイント
エンドポイントとは、push通知の送信先であるデバイス情報のことです。
デバイストークンなどの情報をエンドポイントとしてpinpointに登録すると、pinpointからエンドポイント(デバイス)に対してpush通知を送ることができます。
pinpointに登録方法は、API または AWS SDK を使用して追加できます。今回は、Lambdaを使用して追加します。
カスタム属性
エンドポイントには、デバイスの情報だけでなく、セグメントのフィルタリング時に使用するカスタム属性などを含めることができます。
カスタム属性とは、アプリ内でユーザーが登録している情報になります。例えば、ユーザーのお気に入りに商品や住所などの情報などです。
セグメント
セグメントとは、push通知を送信する対象を、特定の属性や条件でフィルタリングしたグループのことです。
pinpointは、全てのエンドポイントがpinpoint内でDBとして保存されているイメージです。
pinpointに保存されている全てのエンドポイントを基本セグメント
といいます。
基本セグメントから、動的セグメント(後述)によって、push通知先をフィルタリングすることができます。
特定の商品をお気に入りにしているユーザーのみにpush通知を送りたい場合、基本セグメントから、カスタム属性を使用してフィルタリングし、セグメントを作成します。
セグメントを作成する方法は2つ
-
動的セグメント
- エンドポイントのデバイス情報やカスタム属性に基づき、動的に送信先が変化するセグメントです。
- エンドポイントの登録している住所やお気に入りに商品が変わると、定義しているセグメントから外れることもあるため、動的セグメントは時間の経過とともに変化する可能性があります。
- エンドポイントのデバイス情報やカスタム属性に基づき、動的に送信先が変化するセグメントです。
-
静的セグメント
- エンドポイントのデバイス情報やカスタム属性を事前に記載したファイルに基づいて作成されるセグメントです。
- インポートセグメントを作成するときは、S3 にファイルをアップロードします。静的なので、時間の経過とともに変更はされません。
今回こちらは使用しません。
キャンペーン
キャンペーンでは、定義したスケジュールに従ってカスタマイズされたメッセージを送信することができます。
キャンペーンで設定できる機能は、以下となっております。
- 送信方法を決める
- push通知、SMS、アプリ内メッセージ、Email
- push通知時に設定できるメッセージ内容
- タイトルや本文
- 通知のスケジュール
- 頻度、日時、
- セグメントを選択し、通知先を指定する
- 1秒間あたりに送る件数変えられる
また、キャンペーンでpush通知すると、push通知によってアプリを開いたユーザーの割合等を分析することが可能です。
プロジェクト作成
プロジェクト名を入力します
push通知を選択します
APNs
と FC
M のセットアップ方法は、こちらの記事を参考にしてください。
また、Lambdaで必要になるプロジェクトID
をコピーしておいてください。
Lambdaを作成
Lambda側で行うことは以下の通りです。
- Pinpointに対してエンドポイントを登録する
- Pinpointに対してセグメントを登録する
- Pinpointに対してキャンペーンを登録する
になります。
さらに、登録だけでなく、更新と削除も可能です。
-
Pinpointに対してエンドポイントを更新する
-
Pinpointに対してセグメントを更新する
-
Pinpointに対してキャンペーンを更新する
-
Pinpointに対してエンドポイントを削除する
-
Pinpointに対してセグメントを削除する
-
Pinpointに対してキャンペーンを削除する
Lambdaの基本設定
IAMロール
デフォルトのLambdaのIAMロールに以下のIAM権限を付与させます。
<アカウントID>
は、アカウントIDになります
{
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"mobiletargeting:UpdateSegment",
"mobiletargeting:UpdateCampaign",
"mobiletargeting:GetSegment",
"mobiletargeting:GetCampaign",
"mobiletargeting:DeleteCampaign",
"mobiletargeting:DeleteSegment"
],
"Resource": [
"arn:aws:mobiletargeting:*:<アカウントID>:apps/*/segments/*",
"arn:aws:mobiletargeting:*:<アカウントID>:apps/*/campaigns/*"
]
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"mobiletargeting:CreateCampaign",
"mobiletargeting:CreateSegment",
"mobiletargeting:UpdateSegment",
"mobiletargeting:GetSegment",
"mobiletargeting:DeleteUserEndpoints",
"mobiletargeting:GetEndpoint",
"mobiletargeting:DeleteSegment",
"mobiletargeting:GetUserEndpoints",
"mobiletargeting:DeleteEndpoint",
"mobiletargeting:GetCampaigns",
"mobiletargeting:UpdateCampaign",
"mobiletargeting:GetCampaign",
"mobiletargeting:DeleteCampaign",
"mobiletargeting:GetSegments",
"mobiletargeting:UpdateEndpoint"
],
"Resource": "arn:aws:mobiletargeting:*:<アカウントID>:apps/*"
},
{
"Sid": "VisualEditor2",
"Effect": "Allow",
"Action": [
"mobiletargeting:CreateCampaign",
"mobiletargeting:CreateSegment",
"mobiletargeting:UpdateSegment",
"mobiletargeting:GetSegment",
"mobiletargeting:DeleteUserEndpoints",
"mobiletargeting:GetEndpoint",
"mobiletargeting:DeleteSegment",
"mobiletargeting:GetUserEndpoints",
"mobiletargeting:DeleteEndpoint",
"mobiletargeting:GetCampaigns",
"mobiletargeting:UpdateCampaign",
"mobiletargeting:GetCampaign",
"mobiletargeting:DeleteCampaign",
"mobiletargeting:GetSegments",
"mobiletargeting:UpdateEndpoint"
],
"Resource": "arn:aws:mobiletargeting:*:<アカウントID>:apps/*"
}
]
}
1. Pinpointに対してエンドポイントを登録する
flutter側で、PinpointのAPIを叩くことで、エンドポイントにデバイストークンなどの情報を保存できます。
詳しくは下記の記事が参考になります。
今回、flutterの詳しい実装方法は説明いたしません。
2. Pinpointに対してエンドポイントを更新する
pinpointにエンドポイントを更新するために、flutterからLambdaに対して情報を送信します。
flutterからLambdaを呼ぶ方法として、apigatewayもしくは、Lambda単体でURLによって呼べるようになったため、以下の記事を参考に呼ぶとよいでしょう。
IAM認証を無効にする場合、全世界からアクセスされる可能性があるため、
例えば、lambda内でBasic認証を設定し、flutterからURLでlambdaを呼ぶ際に、ヘッダーにauthorization
必要な値を加えてURLを呼ぶことで、セキュアにする必要がありますね。
エンドポイントを取得するLambdaを作成
エンドポイントを取得するためには、ApplicationId と EndpointIdが必要です。
EndpointId
の取得方法は、flutter側で、getUserEndpointsAPI を利用して、userIdからエンドポイントIdを取得できますが、今回は説明しません。
詳細は、sdkのドキュメントを参照してください
const application_id = process.env.AWS_PINPOINT_PROJECT_ID;
const AWS = require('aws-sdk');
const pinpoint = new AWS.Pinpoint({ region: process.env.AWS_REGION });
const endpoint_id = 'eqmj8wpxszeqy/b3vch04sn41yw' //サンプルデータを使用
exports.handler = async (event) => {
console.log('event:', JSON.stringify(event, null, 2));
const params = {
ApplicationId: application_id,
EndpointId: endpoint_id
};
try {
const response_endpoint = await pinpoint.getEndpoint(params).promise();
console.log('response_endpoint:', JSON.stringify(response_endpoint, null, 2));
} catch (err) {
console.error(err);
}
};
実行するとエンドポイントが取得できます。
今回、上記のEndpointIDは、サンプルCSVのセグメントデータから取得したものを使用しました。
サンプルCSVのセグメントデータを取得する方法
pinpointのコンソールからセグメント作成
に遷移し、サンプルCSVをダウンロードし、そのCSVファイルをドロップし、セグメント作成をクリックします。
pinpoint_example_import
をダウンロードします。
ファイルの左端のid
がEndpointID
になりますので、コピーします。
エンドポイントを更新するLambdaを作成
エンドポイントを更新するためには、ApplicationId と EndpointIdが必要です。
EndpointId
の取得方法は、flutter側で、getUserEndpointsAPI を利用して、userIdからエンドポイントIdを取得できますが、今回は説明しません。
const application_id = process.env.AWS_PINPOINT_PROJECT_ID;
const AWS = require('aws-sdk');
const pinpoint = new AWS.Pinpoint({ region: process.env.AWS_REGION });
const endpoint_id = 'eqmj8wpxszeqy/b3vch04sn41yw'; //サンプルデータを使用
exports.handler = async (event) => {
console.log('event:', JSON.stringify(event, null, 2));
const endpoint_data = {
ChannelType: 'GCM',
Address: 'xxxx',
EndpointStatus: 'ACTIVE',
OptOut: 'NONE',
Location: {
Country: 'JPN',
},
Demographic: {
Make: 'Google',
Platform: 'android',
},
EffectiveDate: '2022-06-02T00:00:00+09:00',
User: {
UserAttributes: {
Item: ['bag', 'shoes'],
Gender: ['woman'],
},
},
};
const params = {
ApplicationId: application_id,
EndpointId: endpoint_id,
EndpointRequest: endpoint_data,
};
try {
const response_endpoint = await pinpoint.updateEndpoint(params).promise();
console.log('response_endpoint:', JSON.stringify(response_endpoint, null, 2));
} catch (err) {
console.error(err);
}
};
上記のコードの内、以下は、ユーザーがお気に入りに登録しているアイテム(bag,shoes)、ユーザーの性別情報になります。(こちらもあくまで想定です。)
User: {
UserAttributes: {
Item: ['bags', 'shoes'],
Gender: ['woman'],
},
},
実行すると、レスポンスでAccepted
になっており、エンドポイントの値を変更に成功しました。
response_endpoint: {
"MessageBody": {
"Message": "Accepted",
"RequestID": "07585a26-c5e2-42d6-8aab-7a4973c15baa"
}
}
updateEndpoint
の詳細は、sdkのドキュメントを参照してください
3. Pinpointに対してセグメントを登録する
Lambdaからapiに対してセール対象の商品情報を受け取り、セール対象のアイテムをお気に入り登録しているユーザーのみにpush通知されるよう、セグメントを作成します。(あくまで想定です。)
セグメントは、バッグまたはパンツをお気に入りに登録している女性に対して、セグメントを作成します。
つまり、ユーザー属性がItem
のうちbags,もしくはpants
を登録し、
ユーザー属性がGender
のうちwoman
を登録している女性のセグメントをLambdaで作成するということです。
Lambdaが受け取るevent内容
{
"item": ["bags","pants"],
"gender": "woman"
}
セグメントのみを登録するLambdaを作成
詳細は、sdkのドキュメントを参照してください
const application_id = process.env.AWS_PINPOINT_PROJECT_ID;
const AWS = require('aws-sdk');
const pinpoint = new AWS.Pinpoint({ region: process.env.AWS_REGION });
exports.handler = async (event) => {
console.log('event:', JSON.stringify(event, null, 2));
const segment_data = {
Name: 'favorite_segment',
Dimensions: {
UserAttributes: {
Item: {
AttributeType: 'INCLUSIVE',
Values: event['item'],
},
Gender: {
AttributeType: 'INCLUSIVE',
Values: event['gender'].split(','),
},
},
},
};
try {
const params_segment = {
ApplicationId: application_id,
WriteSegmentRequest: segment_data,
};
const segment_response = await pinpoint.createSegment(params_segment).promise();
console.log('segment_response:', JSON.stringify(segment_response, null, 2));
return pinpoint_response['SegmentResponse']['Id'];
} catch (err) {
console.error(err);
}
};
returnでは 作成したセグメントID
を返しています。
作成できていることを確認しました。
Values内は、配列
注意点として、Values
内は、配列になっていることに気をつけましょう。
Values: event['item']
-
Values: event['gender'].split(',')
↑にするためsplit
メソッドを使用して、文字列を配列化するよう処理しています。
AttributeType
AttributeType: 'INCLUSIVE',
は、Values
と一致する属性値をもつエンドポイントがセグメントに入ります。
EXCLUSIVE
の場合、Values
と一致しない属性値をもつエンドポイントがセグメントに入ります。
4. Pinpointに対して、セグメントを作成し、キャンペーンを登録する
先程セグメントのみを作成するLambdaを作成しましたが、実際には、セグメントを作成し、キャンペーン作成までを1つのLambdaで作成させるのがよいでしょう。
Lambdaが受け取るevent内容
先程と同じです。
{
"item": ["bags","pants"],
"gender": "woman"
}
セグメントを作成し、キャンペーンを登録するLambdaを作成
const application_id = process.env.AWS_PINPOINT_PROJECT_ID;
const AWS = require('aws-sdk');
const pinpoint = new AWS.Pinpoint({ region: process.env.AWS_REGION });
function write_campaign_data(
messages_per_second,
ttl,
push_title,
campaign_title,
push_body,
schedule,
segment_id
) {
const APNS_row_content = {
aps: {
alert: {
title: push_title,
body: push_body,
},
sound: 'default',
content_available: true,
},
};
const GCM_row_content = {
data: {
title: push_title,
body: push_body,
jsonBody: {
type: campaign_title,
},
},
};
return {
AdditionalTreatments: [],
Description: '',
HoldoutPercent: 0,
IsPaused: false,
Limits: {
Daily: 0,
MaximumDuration: 10800,
MessagesPerSecond: messages_per_second,
Total: 0,
},
MessageConfiguration: {
APNSMessage: {
Action: 'OPEN_APP',
TimeToLive: ttl,
RawContent: JSON.stringify(APNS_row_content),
},
GCMMessage: {
Action: 'OPEN_APP',
TimeToLive: ttl,
RawContent: JSON.stringify(GCM_row_content),
},
},
Name: campaign_title,
Schedule: {
Frequency: 'ONCE',
StartTime: schedule,
Timezone: 'UTC+09',
},
SegmentId: segment_id,
SegmentVersion: 1,
};
}
exports.handler = async (event) => {
console.log('event:', JSON.stringify(event, null, 2));
const segment_data = {
Name: 'favorite_segment',
Dimensions: {
UserAttributes: {
Item: {
AttributeType: 'INCLUSIVE',
Values: event['item'],
},
Gender: {
AttributeType: 'INCLUSIVE',
Values: event['gender'].split(','),
},
},
},
};
let response_segment_id;
try {
const params_segment = {
ApplicationId: application_id,
WriteSegmentRequest: segment_data,
};
const segment_response = await pinpoint.createSegment(params_segment).promise();
console.log('segment_response:', JSON.stringify(segment_response, null, 2));
response_segment_id = segment_response['SegmentResponse']['Id'];
} catch (err) {
console.error(err);
}
const campaign_data = write_campaign_data(
100, // int:MessagesPerSecond
3600, // int:TimeToLive
'test_title', // str:push_title
'test_campaign', // str:campaign_title
'test_body', // str:push_body
'IMMEDIATE', // str:schedule
response_segment_id // str:segment_id
);
console.log('campaign_data:', JSON.stringify(campaign_data, null, 2));
try {
const params_campaign = {
ApplicationId: application_id,
WriteCampaignRequest: campaign_data,
};
await pinpoint.createCampaign(params_campaign).promise();
} catch (err) {
console.error(err);
}
};
キャンペーンの設定内容詳細
上記のコードの内、キャンペーンの設定内容詳細であるfunction write_campaign_data
についてを説明します。
通知タイプ
プッシュ通知には、通知タイプが3つあります。
- 標準メッセージ
- メッセージのタイトルと本文のほか、受信者が通知を開いたときに発生するアクションを指定するタイプ
- サイレント通知
- 受信者のデバイスに通知を表示せずにユーザーのアプリケーションに送信されるタイプ
- raw メッセージ
- JSON オブジェクトにプッシュ通知の raw コンテンツを指定するタイプ
私の場合、標準メッセージタイプだと、IOSにpush通知した際、デバイス側で通知音がしなかったため、rawメッセージタイプを選択しました。
rawメッセージタイプだと、通知音が指定できるため、IOSにpush通知した際、push通知音が出ました。
android側のjsonBody
android側には、jsonBodyにtype: campaign_title
を入れています。
これは、fluuter側の事情で、私の場合、キャンペーンタイトル情報を保持する必要があったためです。jsonBody自体なくても問題ありません。
function write_campaign_data(
messages_per_second,
ttl,
push_title,
campaign_title,
push_body,
schedule,
segment_id
) {
const APNS_row_content = {
aps: {
alert: {
title: push_title,
body: push_body,
},
sound: 'default', // push通知音を指定
content_available: true,
},
};
const GCM_row_content = {
data: {
title: push_title,
body: push_body,
jsonBody: {
type: campaign_title, //android側にtitle情報を保持する必要があったため
},
},
};
その他キャンペーン内容で設定できる数値
push通知のキャンペーン内容で設定できる値を説明します。
-
Limits['MaximumDuration']
- スケジュールされた開始時刻以降に各キャンペーンがメッセージの配信を試行できる最大秒数。
- メッセージの配信が失敗した際、再試行する制限時間を設定できます
-
Limits['MessagesPerSecond']
- 1秒間にエンドポイント(デバイス)に送信する件数
- 1~25000件まで選択可能
-
MessageConfiguration['TimeToLive']
- Pinpoint がメッセージを削除するまでにかかる時間 (秒)
- この時間が経過すると、Amazon Pinpoint はメッセージを削除し、再配信の試行を行いません。
-
MessageConfiguration['RawContent']
- push通知の通知タイプ
-
Name
- キャンペーン名
-
Schedule['StartTime]
- キャンペーンを送る日時
- 'IMMEDIATE':即時
- "2022-01-01T19:00:00+09:00": 2022年1月1日19時にキャンペーンを送信
- キャンペーンを送る日時
-
Schedule['Frequency]
- キャンペーンの頻度
- 'ONCE':1回のみ
- 'DAILY':毎日
- キャンペーンの頻度
-
SegmentId
- Lambdaで作成したセグメントのID
return {
AdditionalTreatments: [],
Description: '',
HoldoutPercent: 0,
IsPaused: false,
Limits: {
Daily: 0,
MaximumDuration: 10800,
MessagesPerSecond: messages_per_second,
Total: 0,
},
MessageConfiguration: {
APNSMessage: {
Action: 'OPEN_APP',
TimeToLive: ttl,
RawContent: JSON.stringify(APNS_row_content),
},
GCMMessage: {
Action: 'OPEN_APP',
TimeToLive: ttl,
RawContent: JSON.stringify(GCM_row_content),
},
},
Name: campaign_title,
Schedule: {
Frequency: 'ONCE',
StartTime: schedule,
Timezone: 'UTC+09',
},
SegmentId: segment_id,
SegmentVersion: 1,
};
}
詳細は、sdkのドキュメントを参照してください
長くなったため、次回、「セグメント、キャンペーン」の「更新、削除」をそれぞれ行います。
↓続きは、こちらの記事です。
参考
- async await promise()
- IOSのプッシュ通知音の設定について