LoginSignup
6
9

More than 1 year has passed since last update.

Flutter から Lambda を経由し Amazon Pinpoint のPush通知を実装 ①

Last updated at Posted at 2022-06-18

はじめに

flutterでpush通知の実装を目的に、Lambdaを経由してAmazon Pinpointでpush通知する構築方法についてまとめます。

  1. pinpointに対して、エンドポイントの登録
    スクリーンショット 2022-06-18 18.11.27.png
  2. pinpointに対して、エンドポイントの更新
    スクリーンショット 2022-06-18 18.12.28.png
  3. pinpointに対して、セグメントを作成
    スクリーンショット 2022-06-22 22.57.19.png
  4. pinpointに対して、セグメントとキャンペーンを作成
    スクリーンショット 2022-06-16 21.21.12.png
  5. pinpointに対して、セグメントとキャンペーンを更新
    スクリーンショット 2022-06-18 16.52.45.png
  6. pinpointに対して、セグメントとキャンペーンを削除
    スクリーンショット 2022-06-18 16.53.34.png

前提条件

  • 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通知によってアプリを開いたユーザーの割合等を分析することが可能です。

プロジェクト作成

プロジェクト名を入力します
スクリーンショット 2022-06-16 21.05.48.png
push通知を選択します
スクリーンショット 2022-06-16 21.06.34.png
APNs と FCM のセットアップ方法は、こちらの記事を参考にしてください。
スクリーンショット 2022-06-16 21.07.14.png
また、Lambdaで必要になるプロジェクトIDをコピーしておいてください。
スクリーンショット 2022-06-16 21.22.47.png

Lambdaを作成

Lambda側で行うことは以下の通りです。

  • Pinpointに対してエンドポイントを登録する
  • Pinpointに対してセグメントを登録する
  • Pinpointに対してキャンペーンを登録する
    になります。

さらに、登録だけでなく、更新と削除も可能です。

  • Pinpointに対してエンドポイントを更新する

  • Pinpointに対してセグメントを更新する

  • Pinpointに対してキャンペーンを更新する

  • Pinpointに対してエンドポイントを削除する

  • Pinpointに対してセグメントを削除する

  • Pinpointに対してキャンペーンを削除する

Lambdaの基本設定

Node.jsで開発します。
スクリーンショット 2022-06-16 22.01.35.png

  • タイムアウト30秒に変更
  • 環境変数にpinpointのIDを追加します。
    スクリーンショット 2022-06-16 22.03.27.png

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に対してエンドポイントを登録する

スクリーンショット 2022-06-18 18.11.27.png

flutter側で、PinpointのAPIを叩くことで、エンドポイントにデバイストークンなどの情報を保存できます。
詳しくは下記の記事が参考になります。
今回、flutterの詳しい実装方法は説明いたしません。

2. Pinpointに対してエンドポイントを更新する

スクリーンショット 2022-06-18 18.12.28.png

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ファイルをドロップし、セグメント作成をクリックします。

スクリーンショット 2022-06-18 18.48.13.png

pinpoint_example_importをダウンロードします。

スクリーンショット 2022-06-18 18.48.58.png

ファイルの左端のidEndpointIDになりますので、コピーします。

スクリーンショット 2022-06-18 18.50.54.png

エンドポイントを更新する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に対してセグメントを登録する

スクリーンショット 2022-06-18 15.53.05.png

Lambdaからapiに対してセール対象の商品情報を受け取り、セール対象のアイテムをお気に入り登録しているユーザーのみにpush通知されるよう、セグメントを作成します。(あくまで想定です。)

セグメントは、バッグまたはパンツをお気に入りに登録している女性に対して、セグメントを作成します。

つまり、ユーザー属性がItemのうちbags,もしくはpantsを登録し、
ユーザー属性がGenderのうちwomanを登録している女性のセグメントをLambdaで作成するということです。

Lambdaが受け取るevent内容

{
  "item": ["bags","pants"],
  "gender": "woman"
}

スクリーンショット 2022-06-18 15.51.51.png

セグメントのみを登録する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を返しています。
作成できていることを確認しました。
スクリーンショット 2022-06-18 15.46.23.png

Values内は、配列

注意点として、Values内は、配列になっていることに気をつけましょう。

  • Values: event['item']
  • Values: event['gender'].split(',')
    ↑にするためsplitメソッドを使用して、文字列を配列化するよう処理しています。

AttributeType

AttributeType: 'INCLUSIVE',は、Valuesと一致する属性値をもつエンドポイントがセグメントに入ります。
EXCLUSIVEの場合、Valuesと一致しない属性値をもつエンドポイントがセグメントに入ります。

4. Pinpointに対して、セグメントを作成し、キャンペーンを登録する

先程セグメントのみを作成するLambdaを作成しましたが、実際には、セグメントを作成し、キャンペーン作成までを1つのLambdaで作成させるのがよいでしょう。
スクリーンショット 2022-06-18 15.40.16.png

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);
  }
};

キャンペーンが作成されました。
スクリーンショット 2022-06-18 16.12.36.png

キャンペーンの設定内容詳細

上記のコードの内、キャンペーンの設定内容詳細である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のプッシュ通知音の設定について
6
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
9