From 7add0cc4b531d4598d4d3e7e101216f056464382 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Thu, 5 Jun 2025 14:44:59 -0700 Subject: [PATCH 01/13] added banner component for Pinpoint EOL, put this banner on analytics and push notification pages --- src/components/Layout/Layout.tsx | 5 ++ .../PinpointEOLBanner/PinpointEOLBanner.tsx | 52 +++++++++++++++++++ src/components/PinpointEOLBanner/index.ts | 1 + 3 files changed, 58 insertions(+) create mode 100644 src/components/PinpointEOLBanner/PinpointEOLBanner.tsx create mode 100644 src/components/PinpointEOLBanner/index.ts diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index b3419509203..0ac4e8e01a3 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -37,6 +37,7 @@ import { } from '@/components/NextPrevious'; import { Modal } from '@/components/Modal'; import { Gen1Banner } from '@/components/Gen1Banner'; +import { PinpointEOLBanner } from '@/components/PinpointEOLBanner'; import { ApiModalProvider } from '../ApiDocs/ApiModalProvider'; export const Layout = ({ @@ -281,6 +282,10 @@ export const Layout = ({ {(isGen1GettingStarted || isGen1HowAmplifyWorks) && ( )} + {(asPathWithNoHash.includes('/push-notifications/') || + asPathWithNoHash.includes('/analytics/')) && ( + + )} {children} {showNextPrev && } diff --git a/src/components/PinpointEOLBanner/PinpointEOLBanner.tsx b/src/components/PinpointEOLBanner/PinpointEOLBanner.tsx new file mode 100644 index 00000000000..36c29347e5e --- /dev/null +++ b/src/components/PinpointEOLBanner/PinpointEOLBanner.tsx @@ -0,0 +1,52 @@ +import { Callout } from '@/components/Callout'; +import Link from 'next/link'; +import classNames from 'classnames'; + +export const PinpointEOLBanner = () => { + return ( + + + AWS will end support for Amazon Pinpoint on October 30, 2026, + + , and is no longer accepting any new users as of May 20 (see the linked + doc). The guidance is to use{' '} + + AWS End User Messaging + {' '} + for push notifications and SMS,{' '} + + Amazon Simple Email Service + {' '} + for sending emails,{' '} + + Amazon Connect + {' '} + for campaigns, journeys, endpoints, and engagement analytics. Pinpoint + recommends{' '} + + Amazon Kinesis + {' '} + for event collection and mobile analytics. + + ); +}; diff --git a/src/components/PinpointEOLBanner/index.ts b/src/components/PinpointEOLBanner/index.ts new file mode 100644 index 00000000000..5730022cc7b --- /dev/null +++ b/src/components/PinpointEOLBanner/index.ts @@ -0,0 +1 @@ +export { PinpointEOLBanner } from './PinpointEOLBanner'; From 780284f2340df2831e43f1f41e227bafb3b2ff6f Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Tue, 8 Jul 2025 12:28:06 -0700 Subject: [PATCH 02/13] added documentation for flutter querying by secondary indexes in the data category --- .../data-modeling/secondary-index/index.mdx | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx index 35d27a0d20e..e8102fc0355 100644 --- a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx @@ -104,6 +104,28 @@ let queriedCustomers = try await Amplify.API.query( ``` + + + +The example client query below allows you to query for "Customer" records based on their `accountRepresentativeId`: + +```dart title="lib/main.dart" +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'amplify_outputs.dart'; +import 'models/ModelProvider.dart'; + +// highlight-start +final request = ModelQueries.list( + Customer.classType, + where: accountRepresentativeId.eq(YOUR_REP_ID), +); +// highlight-end + +``` + + + Amplify uses Amazon DynamoDB tables as the default data source for `a.model()`. For key-value databases, it is critical to model your access patterns with "secondary indexes". Use the `.secondaryIndexes()` modifier to configure a secondary index. @@ -185,6 +207,27 @@ let queriedCustomers = try await Amplify.API.query( ``` + + +The example client query below allows you to query for "Customer" records based on their `accountRepresentativeId`: + +```dart title="lib/main.dart" +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'amplify_outputs.dart'; +import 'models/ModelProvider.dart'; + +// highlight-start +final request = ModelQueries.list( + Customer.classType, + where: Customer.accountRepresentativeId.eq(YOUR_REP_ID) & Customer.name.begins("Rene"), +); +// highlight-end + +``` + + + ## Customize the query field for secondary indexes You can also customize the auto-generated query name under `client.models..listBy...` by setting the `queryField()` modifier. @@ -206,7 +249,7 @@ const schema = a.schema({ }); ``` - + In your client app code, you'll see query updated under the Data client: From 927c706d3949dbc0975398c3afeb18fbc87a82d1 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Tue, 8 Jul 2025 12:43:44 -0700 Subject: [PATCH 03/13] removed duplicated flutter inline filters --- .../data/data-modeling/secondary-index/index.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx index e8102fc0355..619c7a73cdd 100644 --- a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx @@ -48,7 +48,7 @@ export const schema = a.schema({ }); ``` - + The example client query below allows you to query for "Customer" records based on their `accountRepresentativeId`: @@ -155,7 +155,7 @@ export const schema = a.schema({ }); ``` - + On the client side, you should find a new `listBy...` query that's named after hash key and sort keys. For example, in this case: `listByAccountRepresentativeIdAndName`. You can supply the filter as part of this new list query: @@ -209,7 +209,7 @@ let queriedCustomers = try await Amplify.API.query( -The example client query below allows you to query for "Customer" records based on their `accountRepresentativeId`: +The example client query below allows you to query for "Customer" records based on their "name" AND their `accountRepresentativeId`: ```dart title="lib/main.dart" import 'package:amplify_api/amplify_api.dart'; From d5b41515f18d0668c9d19aa10c8d47b1a4497883 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Tue, 8 Jul 2025 12:44:55 -0700 Subject: [PATCH 04/13] changed formatting on name key in text --- .../data/data-modeling/secondary-index/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx index 619c7a73cdd..da64d33df56 100644 --- a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx @@ -209,7 +209,7 @@ let queriedCustomers = try await Amplify.API.query( -The example client query below allows you to query for "Customer" records based on their "name" AND their `accountRepresentativeId`: +The example client query below allows you to query for "Customer" records based on their `name` AND their `accountRepresentativeId`: ```dart title="lib/main.dart" import 'package:amplify_api/amplify_api.dart'; From 433affeb5feacf303f79c7216c72c9047ff116d3 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Wed, 9 Jul 2025 16:59:38 -0700 Subject: [PATCH 05/13] Added documentation for using queryFields --- .../data-modeling/secondary-index/index.mdx | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx index da64d33df56..407208c0721 100644 --- a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx @@ -112,13 +112,12 @@ The example client query below allows you to query for "Customer" records based ```dart title="lib/main.dart" import 'package:amplify_api/amplify_api.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; -import 'amplify_outputs.dart'; import 'models/ModelProvider.dart'; // highlight-start final request = ModelQueries.list( Customer.classType, - where: accountRepresentativeId.eq(YOUR_REP_ID), + where: Customer.ACCOUNTREPRESENTATIVEID.eq(YOUR_REP_ID), ); // highlight-end @@ -214,13 +213,12 @@ The example client query below allows you to query for "Customer" records based ```dart title="lib/main.dart" import 'package:amplify_api/amplify_api.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; -import 'amplify_outputs.dart'; import 'models/ModelProvider.dart'; // highlight-start final request = ModelQueries.list( Customer.classType, - where: Customer.accountRepresentativeId.eq(YOUR_REP_ID) & Customer.name.begins("Rene"), + where: Customer.ACCOUNTREPRESENTATIVEID.eq(YOUR_REP_ID) & Customer.NAME.beginsWith("Rene"), ); // highlight-end @@ -301,6 +299,43 @@ let queriedCustomers = try await Amplify.API.query( ``` + + +In your client app code, you can use the updated query name. + +```dart title="lib/main.dart" +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'models/ModelProvider.dart'; + +var accountRepresentativeId = "John"; +var operationName = "listByRep"; +var document = """ + query ListByRep { + $operationName(accountRepresentativeId: "$accountRepresentativeId") { + items { + accountRepresentativeId + createdAt + id + name + phoneNumber + updatedAt + } + nextToken + } + } + """; + +final request = GraphQLRequest( + document: document, + variables: { + 'accountRepresentativeId': accountRepresentativeId, + 'operationName': operationName, + }, +); +``` + + ## Customize the name of secondary indexes From 94849f04cf0cd207f2f6ccddf4311057aa938a8e Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:31:16 -0700 Subject: [PATCH 06/13] ignore claimed spelling mistake by yarn spellcheck --- .../data/data-modeling/secondary-index/index.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx index 407208c0721..a69f4ee72fa 100644 --- a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx @@ -1,5 +1,4 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - export const meta = { title: 'Customize secondary indexes', description: @@ -117,6 +116,7 @@ import 'models/ModelProvider.dart'; // highlight-start final request = ModelQueries.list( Customer.classType, + // eslint-disable-next-line spellcheck/spell-checker where: Customer.ACCOUNTREPRESENTATIVEID.eq(YOUR_REP_ID), ); // highlight-end @@ -218,6 +218,7 @@ import 'models/ModelProvider.dart'; // highlight-start final request = ModelQueries.list( Customer.classType, + // eslint-disable-next-line spellcheck/spell-checker where: Customer.ACCOUNTREPRESENTATIVEID.eq(YOUR_REP_ID) & Customer.NAME.beginsWith("Rene"), ); // highlight-end From bf23811c63f1c72a36cf0e41700e72f4d0b8e719 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:37:38 -0700 Subject: [PATCH 07/13] ignore claimed spelling mistake by yarn spellcheck --- .../data/data-modeling/secondary-index/index.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx index a69f4ee72fa..2bda0bda72d 100644 --- a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx @@ -1,4 +1,5 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; +// cspell:ignore ACCOUNTREPRESENTATIVEID export const meta = { title: 'Customize secondary indexes', description: @@ -116,7 +117,6 @@ import 'models/ModelProvider.dart'; // highlight-start final request = ModelQueries.list( Customer.classType, - // eslint-disable-next-line spellcheck/spell-checker where: Customer.ACCOUNTREPRESENTATIVEID.eq(YOUR_REP_ID), ); // highlight-end @@ -218,7 +218,6 @@ import 'models/ModelProvider.dart'; // highlight-start final request = ModelQueries.list( Customer.classType, - // eslint-disable-next-line spellcheck/spell-checker where: Customer.ACCOUNTREPRESENTATIVEID.eq(YOUR_REP_ID) & Customer.NAME.beginsWith("Rene"), ); // highlight-end From e4e1ca818f69fc934c07e28d11afea1c5d97c821 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Tue, 29 Jul 2025 12:54:13 -0700 Subject: [PATCH 08/13] updated kotlin version in platform setup --- src/pages/[platform]/start/platform-setup/index.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/[platform]/start/platform-setup/index.mdx b/src/pages/[platform]/start/platform-setup/index.mdx index b9924bc9bf0..6565c28b022 100644 --- a/src/pages/[platform]/start/platform-setup/index.mdx +++ b/src/pages/[platform]/start/platform-setup/index.mdx @@ -48,7 +48,7 @@ Select Runner, Project -> Runner and then the "Build Settings" tab. Update "iOS ## Android -Amplify Flutter supports API level 24+ (Android 7.0+), and requires Gradle 8+, Kotlin 1.9+, and Java 17+ when targeting Android. Follow the steps below to apply these changes in your app. +Amplify Flutter supports API level 24+ (Android 7.0+), and requires Gradle 8+, Kotlin 2.2+, and Java 17+ when targeting Android. Follow the steps below to apply these changes in your app. The steps below are intended for Flutter apps created with Flutter version 3.16+. If your app was created prior to version 3.16, please follow the guide [here](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) to migrate to Gradle's declarative plugins block before following the steps below. @@ -64,7 +64,7 @@ plugins { - id("com.android.application") version "8.7.0" apply false - id("org.jetbrains.kotlin.android") version "1.8.22" apply false + id("com.android.application") version "8.3.0" apply false -+ id("org.jetbrains.kotlin.android") version "1.9.10" apply false ++ id("org.jetbrains.kotlin.android") version "2.2.0" apply false } ``` @@ -129,7 +129,7 @@ plugins { - id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false + id "com.android.application" version "8.3.0" apply false -+ id "org.jetbrains.kotlin.android" version "1.9.10" apply false ++ id "org.jetbrains.kotlin.android" version "2.2.0" apply false } ``` From 8f113a3c43e72b1d693ba74777e0c3b005e6536c Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Mon, 4 May 2026 14:30:07 -0700 Subject: [PATCH 09/13] Add Pinpoint push campaign migration guide to Connect --- .../analytics/pinpoint-migration/index.mdx | 279 ++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx new file mode 100644 index 00000000000..b8c5d2b1295 --- /dev/null +++ b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx @@ -0,0 +1,279 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Migrate from Pinpoint push campaigns to Amazon Connect', + description: 'Guide for migrating Pinpoint push notification campaigns to Amazon Connect Journeys with End User Messaging', + route: "/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration", + platforms: [ + 'flutter', + 'swift', + 'android' + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + meta + } + }; +} + +[AWS will end support for Amazon Pinpoint on October 30, 2026](https://docs.aws.amazon.com/pinpoint/latest/userguide/migrate.html). This guide covers migrating your Pinpoint push notification campaigns to Amazon Connect, using End User Messaging (the rebranded Pinpoint messaging API) for push delivery. + +## What doesn't change + +Firebase Cloud Messaging (FCM) and Apple Push Notification service (APNs) still deliver pushes to devices. Pinpoint was never sending pushes directly — it called FCM/APNs via End User Messaging. **That API is not deprecated.** Your on-device push handling code (notification display, tap handling, deep linking, permissions) does not need to change. + +What changes is how pushes are **triggered** (Connect Journeys instead of Pinpoint campaigns) and how device tokens are **registered** (Connect Customer Profiles instead of Pinpoint endpoints). + +## Choose your migration path + +### Option A: Direct send (no campaign UI needed) + +If you already manage your own device token lists and just need to send pushes, you can skip Connect entirely and call End User Messaging directly. This is the simplest migration. + +```python +import boto3 + +eum = boto3.client('pinpoint', region_name='us-east-1') +APP_ID = 'your-end-user-messaging-app-id' + +devices = [ + {'token': 'fcm-token-abc123', 'channel': 'GCM'}, + {'token': 'apns-token-xyz789', 'channel': 'APNS'}, +] + +for device in devices: + response = eum.send_messages( + ApplicationId=APP_ID, + MessageRequest={ + 'Addresses': { + device['token']: {'ChannelType': device['channel']} + }, + 'MessageConfiguration': { + 'GCMMessage': { + 'Title': 'Your notification title', + 'Body': 'Your notification body', + 'Action': 'OPEN_APP', + }, + 'APNSMessage': { + 'Title': 'Your notification title', + 'Body': 'Your notification body', + 'Action': 'OPEN_APP', + } + } + } + ) +``` + +**Pros:** Minimal migration. No Connect setup. Keep your existing workflow. + +**Cons:** No segmentation UI, no campaign scheduling, no targeting by user attributes. You manage everything yourself. + +### Option B: Connect Journeys (for segmentation and campaign UI) + +If you want to target users by attributes and use a campaign management UI, migrate to Amazon Connect. + +#### Step 1: Create a Connect Customer Profiles domain + +```bash +aws customer-profiles create-domain \ + --domain-name my-push-domain \ + --default-expiration-days 365 \ + --region us-east-1 +``` + +#### Step 2: Create the device profile object type + +This tells Connect how to store device tokens on user profiles. + +```bash +aws customer-profiles put-profile-object-type \ + --domain-name my-push-domain \ + --object-type-name AmplifyDevice \ + --description "Mobile device for push notifications" \ + --keys '{ + "deviceIdKey": [{"StandardIdentifiers": ["UNIQUE"], "FieldNames": ["deviceId"]}], + "userIdKey": [{"StandardIdentifiers": ["PROFILE"], "FieldNames": ["userId"]}] + }' \ + --fields '{ + "deviceId": {"Source": "_source.deviceId", "Target": "_source.deviceId", "ContentType": "STRING"}, + "deviceToken": {"Source": "_source.deviceToken", "Target": "_source.deviceToken", "ContentType": "STRING"}, + "channelType": {"Source": "_source.channelType", "Target": "_source.channelType", "ContentType": "STRING"}, + "platform": {"Source": "_source.platform", "Target": "_source.platform", "ContentType": "STRING"}, + "userId": {"Source": "_source.userId", "Target": "_source.userId", "ContentType": "STRING"} + }' \ + --allow-profile-creation \ + --region us-east-1 +``` + +#### Step 3: Export and import your Pinpoint endpoints + +Follow the [official Pinpoint migration guide](https://docs.aws.amazon.com/pinpoint/latest/userguide/migrate.html#migration-steps) to export your endpoints, then import them as Customer Profiles. For push endpoints (GCM/APNS), also register them as device profile objects: + +```python +import boto3, json + +cp = boto3.client('customer-profiles', region_name='us-east-1') +DOMAIN = 'my-push-domain' + +# For each push device from your Pinpoint export: +cp.put_profile_object( + DomainName=DOMAIN, + ObjectTypeName='AmplifyDevice', + Object=json.dumps({ + 'userId': 'user-001', + 'deviceId': 'pinpoint-endpoint-id', + 'deviceToken': 'fcm-or-apns-token', + 'channelType': 'GCM', + 'platform': 'Android', + }) +) +``` + +#### Step 4: Deploy the push delivery Lambda + +Amazon Connect Journeys support email, SMS, WhatsApp, and voice as native channels — but **not push notifications**. To send pushes from a Journey, you need a Lambda function that bridges the gap. The Journey invokes this Lambda via a "Custom Action" block, and the Lambda reads device tokens from the user's profile objects and calls End User Messaging (the rebranded Pinpoint messaging API) to deliver the push via FCM/APNs. + +Here's the Lambda implementation: + +```python +# push_delivery_lambda.py +import boto3 +import json + +cp = boto3.client('customer-profiles') +eum = boto3.client('pinpoint') # End User Messaging uses the same SDK client + +DOMAIN = 'my-push-domain' +EUM_APP_ID = 'your-end-user-messaging-app-id' + +def handler(event, context): + """ + Invoked by a Connect Journey's Custom Action block. + Receives batched CustomerProfiles, reads their device tokens, + and sends push notifications via End User Messaging. + """ + results = [] + + for record in event.get('CustomerProfiles', []): + profile_id = record['ProfileId'] + + # Get device tokens from profile objects + devices_response = cp.list_profile_objects( + DomainName=DOMAIN, + ObjectTypeName='AmplifyDevice', + ProfileId=profile_id + ) + + for obj in devices_response.get('Items', []): + device = json.loads(obj['Object']) + token = device.get('deviceToken') + channel = device.get('channelType', 'GCM') + + if not token: + continue + + # Build the message for the appropriate platform + msg_config = {} + if channel == 'GCM': + msg_config['GCMMessage'] = { + 'Title': event.get('title', 'Notification'), + 'Body': event.get('body', ''), + 'Action': 'OPEN_APP', + } + elif channel in ('APNS', 'APNS_SANDBOX'): + msg_config['APNSMessage'] = { + 'Title': event.get('title', 'Notification'), + 'Body': event.get('body', ''), + 'Action': 'OPEN_APP', + } + + # Send via End User Messaging (FCM/APNs delivery) + response = eum.send_messages( + ApplicationId=EUM_APP_ID, + MessageRequest={ + 'Addresses': {token: {'ChannelType': channel}}, + 'MessageConfiguration': msg_config, + } + ) + + status = response['MessageResponse']['Result'].get( + token, {} + ).get('DeliveryStatus', 'UNKNOWN') + results.append({ + 'profileId': profile_id, + 'deviceId': device.get('deviceId'), + 'status': status + }) + + return {'results': results} +``` + +Deploy it and wire it to your Connect instance: + +```bash +# Deploy and wire to Connect +aws lambda create-function \ + --function-name push-delivery \ + --runtime python3.12 \ + --handler push_delivery_lambda.handler \ + --role arn:aws:iam::ACCOUNT:role/push-delivery-role \ + --zip-file fileb://lambda.zip + +aws lambda add-permission \ + --function-name push-delivery \ + --statement-id connect-invoke \ + --action lambda:InvokeFunction \ + --principal connect.amazonaws.com + +aws connect associate-lambda-function \ + --instance-id YOUR_CONNECT_INSTANCE_ID \ + --function-arn arn:aws:lambda:us-east-1:ACCOUNT:function:push-delivery +``` + +#### Step 5: Create a Journey and send + +1. Open the Connect console → Outbound campaigns → Create Journey +2. Choose your segment (filter by profile attributes like `plan = premium`) +3. Add a **Custom Action** block → select your `push-delivery` Lambda +4. Run the Journey + +## Registering devices from your mobile app + +Once your backend is set up, each device needs to register its push token with Connect Customer Profiles by calling `PutProfileObject` with the `AmplifyDevice` object type. This can be done via: + +- **Android/Swift:** The AWS SDK for Kotlin or Swift `CustomerProfilesClient.putProfileObject()` directly from the app +- **Flutter:** A backend Lambda proxy, or a SigV4-signed HTTP request to the Customer Profiles REST API +- **Any platform:** A thin backend endpoint that accepts the token and calls `PutProfileObject` on the customer's behalf + +The device object shape is the same as shown in Step 3 above — `userId`, `deviceId`, `deviceToken`, `channelType`, and `platform`. The `UNIQUE` key on `deviceId` means re-registering with the same device ID updates the token in place (handles token refreshes). + + + +**New Amplify libraries for Connect are in development.** When available, they will provide `connectClient.registerDevice(token)` that handles this automatically. This guide will be updated at that time. + + + +## Pinpoint to Connect mapping + +| Pinpoint concept | Connect equivalent | +|---|---| +| Endpoint | Customer Profile + AmplifyDevice profile object | +| Endpoint ID | `deviceId` on AmplifyDevice object | +| Endpoint Address (push token) | `deviceToken` on AmplifyDevice object | +| Endpoint Attributes | Profile Attributes map | +| Segment | Connect Segment (Classic segmentation) | +| Campaign (push) | Journey with Custom Action → Lambda → End User Messaging | +| UpdateEndpoint | `identifyUser()` + `registerDevice()` | + + + +**New Amplify libraries for Connect and push are in development.** When available, they will wrap the manual AWS SDK calls shown above into simple APIs like `connectClient.identifyUser()` and `connectClient.registerDevice()`. This guide will be updated at that time. + + From e7c5207ac6550ebad7b44ddc1bff2bfe9b05f9d9 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Mon, 1 Jun 2026 13:15:01 -0700 Subject: [PATCH 10/13] fix(docs): correct Pinpoint migration guide Lambda format and add missing sections - Fix Lambda event format to use event['Items']['CustomerProfiles'] per AWS docs (was incorrectly using event.get('CustomerProfiles', [])) - Fix Lambda registration: use Connect console Outbound campaigns UI, not associate-lambda-function (which is for contact flows) - Add End User Messaging App ID explanation - Add IAM permissions for Lambda execution role - Add prerequisites section (Connect instance, Customer Profiles) - Add analytics-only migration path (Kinesis Data Streams) - Add identifyUser migration guidance with field mapping - Move notification content to environment variables - Remove duplicate callouts and redundant explanations - Fix boto3 client comment --- .../analytics/pinpoint-migration/index.mdx | 187 ++++++++++++++---- 1 file changed, 146 insertions(+), 41 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx index b8c5d2b1295..c53921bd273 100644 --- a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx +++ b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx @@ -25,6 +25,12 @@ export function getStaticProps(context) { [AWS will end support for Amazon Pinpoint on October 30, 2026](https://docs.aws.amazon.com/pinpoint/latest/userguide/migrate.html). This guide covers migrating your Pinpoint push notification campaigns to Amazon Connect, using End User Messaging (the rebranded Pinpoint messaging API) for push delivery. +## If you only use Amplify Analytics + +If you only use Amplify Analytics (`recordEvent`, session tracking) and don't use push notifications, your migration path is **Amazon Kinesis Data Streams** — not Connect. See [Set up Kinesis Data Streams](/[platform]/build-a-backend/add-aws-services/analytics/streaming-analytics/) for setup instructions. The Amplify Kinesis client is already available. + +The rest of this guide is for teams migrating **push notification campaigns**. + ## What doesn't change Firebase Cloud Messaging (FCM) and Apple Push Notification service (APNs) still deliver pushes to devices. Pinpoint was never sending pushes directly — it called FCM/APNs via End User Messaging. **That API is not deprecated.** Your on-device push handling code (notification display, tap handling, deep linking, permissions) does not need to change. @@ -37,11 +43,17 @@ What changes is how pushes are **triggered** (Connect Journeys instead of Pinpoi If you already manage your own device token lists and just need to send pushes, you can skip Connect entirely and call End User Messaging directly. This is the simplest migration. + + +**End User Messaging App ID:** This is your existing Pinpoint Application ID. After the rebrand, the same ID is used by the End User Messaging APIs. Find it in the AWS console under **End User Messaging → Applications**, or via `aws pinpoint get-apps`. + + + ```python import boto3 eum = boto3.client('pinpoint', region_name='us-east-1') -APP_ID = 'your-end-user-messaging-app-id' +APP_ID = 'your-end-user-messaging-app-id' # Your existing Pinpoint Application ID devices = [ {'token': 'fcm-token-abc123', 'channel': 'GCM'}, @@ -79,6 +91,14 @@ for device in devices: If you want to target users by attributes and use a campaign management UI, migrate to Amazon Connect. +## Prerequisites + +Before starting Option B, you need: + +- **An Amazon Connect instance** with outbound campaigns enabled. Find your instance ID via `aws connect list-instances` or from the Connect console URL (the UUID after `/instance/`). +- **Customer Profiles enabled** and linked to your Connect instance. Enable it in the Connect console under **Customer Profiles**. +- **An End User Messaging app** (your existing Pinpoint Application ID). Find it via `aws pinpoint get-apps` or in the console under **End User Messaging → Applications**. + #### Step 1: Create a Connect Customer Profiles domain ```bash @@ -138,30 +158,83 @@ cp.put_profile_object( #### Step 4: Deploy the push delivery Lambda -Amazon Connect Journeys support email, SMS, WhatsApp, and voice as native channels — but **not push notifications**. To send pushes from a Journey, you need a Lambda function that bridges the gap. The Journey invokes this Lambda via a "Custom Action" block, and the Lambda reads device tokens from the user's profile objects and calls End User Messaging (the rebranded Pinpoint messaging API) to deliver the push via FCM/APNs. +Amazon Connect Journeys support email, SMS, WhatsApp, and voice as native channels — but **not push notifications**. To send pushes from a Journey, you need a Lambda function that bridges the gap. The Journey invokes this Lambda via a "Custom Action" block, and the Lambda reads device tokens from the user's profile objects and calls End User Messaging to deliver the push via FCM/APNs. + +##### Lambda IAM role + +Create an execution role with these permissions: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "mobiletargeting:SendMessages", + "Resource": "arn:aws:mobiletargeting:us-east-1:ACCOUNT:apps/YOUR_EUM_APP_ID/*" + }, + { + "Effect": "Allow", + "Action": "profile:ListProfileObjects", + "Resource": "arn:aws:profile:us-east-1:ACCOUNT:domains/my-push-domain" + }, + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": "arn:aws:logs:us-east-1:ACCOUNT:*" + } + ] +} +``` + +Trust policy: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "lambda.amazonaws.com"}, + "Action": "sts:AssumeRole" + } + ] +} +``` + +##### Lambda implementation -Here's the Lambda implementation: +The Lambda receives a batch of customer profiles from the Journey's Custom Action block. It reads each profile's device tokens and sends push notifications via End User Messaging. Notification content comes from environment variables so you can update it without redeploying code. ```python # push_delivery_lambda.py import boto3 import json +import os cp = boto3.client('customer-profiles') -eum = boto3.client('pinpoint') # End User Messaging uses the same SDK client +# Push delivery uses the mobiletargeting API (boto3.client('pinpoint')), +# which remains available after Pinpoint EOL. +eum = boto3.client('pinpoint') -DOMAIN = 'my-push-domain' -EUM_APP_ID = 'your-end-user-messaging-app-id' +DOMAIN = os.environ['CUSTOMER_PROFILES_DOMAIN'] +EUM_APP_ID = os.environ['EUM_APP_ID'] +NOTIFICATION_TITLE = os.environ.get('NOTIFICATION_TITLE', 'Notification') +NOTIFICATION_BODY = os.environ.get('NOTIFICATION_BODY', '') def handler(event, context): """ - Invoked by a Connect Journey's Custom Action block. - Receives batched CustomerProfiles, reads their device tokens, - and sends push notifications via End User Messaging. + Invoked by a Connect Journey Custom Action block. + Event format: https://docs.aws.amazon.com/connect/latest/adminguide/lambda-invoke-functions.html """ - results = [] + profiles = event['Items']['CustomerProfiles'] + response_items = [] - for record in event.get('CustomerProfiles', []): + for record in profiles: profile_id = record['ProfileId'] # Get device tokens from profile objects @@ -171,6 +244,7 @@ def handler(event, context): ProfileId=profile_id ) + statuses = [] for obj in devices_response.get('Items', []): device = json.loads(obj['Object']) token = device.get('deviceToken') @@ -179,23 +253,21 @@ def handler(event, context): if not token: continue - # Build the message for the appropriate platform msg_config = {} if channel == 'GCM': msg_config['GCMMessage'] = { - 'Title': event.get('title', 'Notification'), - 'Body': event.get('body', ''), + 'Title': NOTIFICATION_TITLE, + 'Body': NOTIFICATION_BODY, 'Action': 'OPEN_APP', } elif channel in ('APNS', 'APNS_SANDBOX'): msg_config['APNSMessage'] = { - 'Title': event.get('title', 'Notification'), - 'Body': event.get('body', ''), + 'Title': NOTIFICATION_TITLE, + 'Body': NOTIFICATION_BODY, 'Action': 'OPEN_APP', } - # Send via End User Messaging (FCM/APNs delivery) - response = eum.send_messages( + resp = eum.send_messages( ApplicationId=EUM_APP_ID, MessageRequest={ 'Addresses': {token: {'ChannelType': channel}}, @@ -203,40 +275,53 @@ def handler(event, context): } ) - status = response['MessageResponse']['Result'].get( + status = resp['MessageResponse']['Result'].get( token, {} ).get('DeliveryStatus', 'UNKNOWN') - results.append({ - 'profileId': profile_id, - 'deviceId': device.get('deviceId'), - 'status': status - }) + statuses.append(status) - return {'results': results} + response_items.append({ + 'Id': profile_id, + 'ResultData': {'deliveryStatuses': statuses} + }) + + return {'Items': {'CustomerProfiles': response_items}} ``` -Deploy it and wire it to your Connect instance: +##### Deploy the Lambda ```bash -# Deploy and wire to Connect +zip lambda.zip push_delivery_lambda.py + aws lambda create-function \ --function-name push-delivery \ --runtime python3.12 \ --handler push_delivery_lambda.handler \ --role arn:aws:iam::ACCOUNT:role/push-delivery-role \ - --zip-file fileb://lambda.zip + --zip-file fileb://lambda.zip \ + --environment 'Variables={CUSTOMER_PROFILES_DOMAIN=my-push-domain,EUM_APP_ID=your-end-user-messaging-app-id,NOTIFICATION_TITLE=Your Title,NOTIFICATION_BODY=Your message body}' aws lambda add-permission \ --function-name push-delivery \ --statement-id connect-invoke \ --action lambda:InvokeFunction \ --principal connect.amazonaws.com - -aws connect associate-lambda-function \ - --instance-id YOUR_CONNECT_INSTANCE_ID \ - --function-arn arn:aws:lambda:us-east-1:ACCOUNT:function:push-delivery ``` +##### Register the Lambda with Connect + + + +The `associate-lambda-function` CLI command is for **contact flows**, not outbound campaigns. For Journey Custom Actions, register your Lambda via the Connect console: + +1. Open the [Amazon Connect console](https://console.aws.amazon.com/connect/) +2. Select your instance +3. In the navigation pane, go to **Channels and communications → Outbound campaigns** +4. Under **Set up custom actions**, use the **Lambda functions** dropdown to select your function +5. Choose **Add Lambda Function** + + + #### Step 5: Create a Journey and send 1. Open the Connect console → Outbound campaigns → Create Journey @@ -244,6 +329,32 @@ aws connect associate-lambda-function \ 3. Add a **Custom Action** block → select your `push-delivery` Lambda 4. Run the Journey +## Migrating `identifyUser` + +Amplify's `identifyUser` API mapped to Pinpoint's `UpdateEndpoint`. The replacement is creating or updating a Connect Customer Profile with the same attributes. + +| Pinpoint (`identifyUser`) field | Connect Customer Profile field | +|---|---| +| `name` | `FirstName` / `LastName` | +| `email` | `EmailAddress` | +| `plan` (custom attribute) | `Attributes.plan` | +| Custom user attributes | `Attributes` map | + +```python +import boto3 + +cp = boto3.client('customer-profiles', region_name='us-east-1') + +# Equivalent of identifyUser({ userId: 'user-001', name: 'Jane Doe', email: '...' }) +cp.create_profile( + DomainName='my-push-domain', + FirstName='Jane', + LastName='Doe', + EmailAddress='jane@example.com', + Attributes={'plan': 'premium', 'locale': 'en_US'} +) +``` + ## Registering devices from your mobile app Once your backend is set up, each device needs to register its push token with Connect Customer Profiles by calling `PutProfileObject` with the `AmplifyDevice` object type. This can be done via: @@ -252,11 +363,11 @@ Once your backend is set up, each device needs to register its push token with C - **Flutter:** A backend Lambda proxy, or a SigV4-signed HTTP request to the Customer Profiles REST API - **Any platform:** A thin backend endpoint that accepts the token and calls `PutProfileObject` on the customer's behalf -The device object shape is the same as shown in Step 3 above — `userId`, `deviceId`, `deviceToken`, `channelType`, and `platform`. The `UNIQUE` key on `deviceId` means re-registering with the same device ID updates the token in place (handles token refreshes). +The device object shape is the same as shown in Step 3 — `userId`, `deviceId`, `deviceToken`, `channelType`, and `platform`. The `UNIQUE` key on `deviceId` means re-registering with the same device ID updates the token in place (handles token refreshes). -**New Amplify libraries for Connect are in development.** When available, they will provide `connectClient.registerDevice(token)` that handles this automatically. This guide will be updated at that time. +**New Amplify libraries for Connect are in development.** When available, they will provide `connectClient.registerDevice(token)` and `connectClient.identifyUser()` that handle this automatically. This guide will be updated at that time. @@ -270,10 +381,4 @@ The device object shape is the same as shown in Step 3 above — `userId`, `devi | Endpoint Attributes | Profile Attributes map | | Segment | Connect Segment (Classic segmentation) | | Campaign (push) | Journey with Custom Action → Lambda → End User Messaging | -| UpdateEndpoint | `identifyUser()` + `registerDevice()` | - - - -**New Amplify libraries for Connect and push are in development.** When available, they will wrap the manual AWS SDK calls shown above into simple APIs like `connectClient.identifyUser()` and `connectClient.registerDevice()`. This guide will be updated at that time. - - +| `identifyUser` / `UpdateEndpoint` | `CreateProfile` / `UpdateProfile` | From 050cc068216103e90de38cf950411192dfbd41b7 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Mon, 1 Jun 2026 13:20:23 -0700 Subject: [PATCH 11/13] docs: add Accordion, BlockSwitcher for better scannability - Wrap IAM policy and Lambda code in Accordion (collapsible) - Add BlockSwitcher with Kotlin/Swift/Dart tabs for device registration - Wrap put-profile-object-type CLI in Accordion - Add summary text outside accordions so readers get context without expanding --- .../analytics/pinpoint-migration/index.mdx | 133 +++++++++++++++++- 1 file changed, 126 insertions(+), 7 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx index c53921bd273..26d03107acf 100644 --- a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx +++ b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx @@ -112,6 +112,8 @@ aws customer-profiles create-domain \ This tells Connect how to store device tokens on user profiles. + + ```bash aws customer-profiles put-profile-object-type \ --domain-name my-push-domain \ @@ -132,6 +134,10 @@ aws customer-profiles put-profile-object-type \ --region us-east-1 ``` + + +This creates an `AmplifyDevice` object type with fields for `deviceId`, `deviceToken`, `channelType`, `platform`, and `userId`. The `UNIQUE` key on `deviceId` ensures one record per device; the `PROFILE` key on `userId` links devices to profiles. + #### Step 3: Export and import your Pinpoint endpoints Follow the [official Pinpoint migration guide](https://docs.aws.amazon.com/pinpoint/latest/userguide/migrate.html#migration-steps) to export your endpoints, then import them as Customer Profiles. For push endpoints (GCM/APNS), also register them as device profile objects: @@ -160,7 +166,7 @@ cp.put_profile_object( Amazon Connect Journeys support email, SMS, WhatsApp, and voice as native channels — but **not push notifications**. To send pushes from a Journey, you need a Lambda function that bridges the gap. The Journey invokes this Lambda via a "Custom Action" block, and the Lambda reads device tokens from the user's profile objects and calls End User Messaging to deliver the push via FCM/APNs. -##### Lambda IAM role + Create an execution role with these permissions: @@ -206,7 +212,9 @@ Trust policy: } ``` -##### Lambda implementation + + + The Lambda receives a batch of customer profiles from the Journey's Custom Action block. It reads each profile's device tokens and sends push notifications via End User Messaging. Notification content comes from environment variables so you can update it without redeploying code. @@ -288,6 +296,8 @@ def handler(event, context): return {'Items': {'CustomerProfiles': response_items}} ``` + + ##### Deploy the Lambda ```bash @@ -357,13 +367,122 @@ cp.create_profile( ## Registering devices from your mobile app -Once your backend is set up, each device needs to register its push token with Connect Customer Profiles by calling `PutProfileObject` with the `AmplifyDevice` object type. This can be done via: +Once your backend is set up, each device needs to register its push token with Connect Customer Profiles by calling `PutProfileObject` with the `AmplifyDevice` object type. + + + + + +```kotlin +import aws.sdk.kotlin.services.customerprofiles.CustomerProfilesClient +import aws.sdk.kotlin.services.customerprofiles.model.PutProfileObjectRequest +import kotlinx.serialization.json.* + +val cpClient = CustomerProfilesClient { region = "us-east-1" } + +suspend fun registerDevice(userId: String, token: String, deviceId: String) { + val deviceObject = buildJsonObject { + put("userId", userId) + put("deviceId", deviceId) + put("deviceToken", token) + put("channelType", "GCM") + put("platform", "Android") + } + + cpClient.putProfileObject(PutProfileObjectRequest { + domainName = "my-push-domain" + objectTypeName = "AmplifyDevice" + `object` = deviceObject.toString() + }) +} +``` + + + + + +```swift +import AWSCustomerProfiles + +let cpClient = try CustomerProfilesClient(region: "us-east-1") + +func registerDevice(userId: String, token: String, deviceId: String) async throws { + let deviceObject: [String: String] = [ + "userId": userId, + "deviceId": deviceId, + "deviceToken": token, + "channelType": "APNS", + "platform": "iOS" + ] + + let objectJson = try JSONSerialization.data(withJSONObject: deviceObject) + + let input = PutProfileObjectInput( + domainName: "my-push-domain", + object: String(data: objectJson, encoding: .utf8)!, + objectTypeName: "AmplifyDevice" + ) + _ = try await cpClient.putProfileObject(input: input) +} +``` + + + + + +```dart +// Flutter apps should call a backend endpoint that proxies to Customer Profiles. +// Example backend Lambda (Python): + +import boto3, json + +cp = boto3.client('customer-profiles', region_name='us-east-1') + +def handler(event, context): + body = json.loads(event['body']) + cp.put_profile_object( + DomainName='my-push-domain', + ObjectTypeName='AmplifyDevice', + Object=json.dumps({ + 'userId': body['userId'], + 'deviceId': body['deviceId'], + 'deviceToken': body['deviceToken'], + 'channelType': body['channelType'], + 'platform': body['platform'], + }) + ) + return {'statusCode': 200} +``` + +```dart +// Flutter client call: +import 'package:http/http.dart' as http; +import 'dart:convert'; + +Future registerDevice({ + required String userId, + required String deviceId, + required String token, + required String platform, +}) async { + await http.post( + Uri.parse('https://your-api.execute-api.us-east-1.amazonaws.com/register-device'), + body: jsonEncode({ + 'userId': userId, + 'deviceId': deviceId, + 'deviceToken': token, + 'channelType': platform == 'android' ? 'GCM' : 'APNS', + 'platform': platform, + }), + ); +} +``` + + -- **Android/Swift:** The AWS SDK for Kotlin or Swift `CustomerProfilesClient.putProfileObject()` directly from the app -- **Flutter:** A backend Lambda proxy, or a SigV4-signed HTTP request to the Customer Profiles REST API -- **Any platform:** A thin backend endpoint that accepts the token and calls `PutProfileObject` on the customer's behalf + -The device object shape is the same as shown in Step 3 — `userId`, `deviceId`, `deviceToken`, `channelType`, and `platform`. The `UNIQUE` key on `deviceId` means re-registering with the same device ID updates the token in place (handles token refreshes). +The `UNIQUE` key on `deviceId` means re-registering with the same device ID updates the token in place (handles token refreshes). From b7045513d891ef9067b30928ae6227eeb742b4fe Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Mon, 1 Jun 2026 13:40:20 -0700 Subject: [PATCH 12/13] fix: address CI check failures - Add platform to getStaticProps props (required by docs framework) - Add page to directory.mjs for sidebar rendering - Add headingLevel/eyebrow to Accordion components per style guide - Add cspell words: customerprofiles, fileb, rebranded --- cspell.json | 673 +++++++++--------- src/directory/directory.mjs | 4 + .../analytics/pinpoint-migration/index.mdx | 7 +- 3 files changed, 350 insertions(+), 334 deletions(-) diff --git a/cspell.json b/cspell.json index c43dce26b34..33d2df8ef0c 100644 --- a/cspell.json +++ b/cspell.json @@ -2,20 +2,6 @@ "version": "0.1", "language": "en", "words": [ - "_", - "_blank", - "_Granting", - "_Identity", - "_new", - "_or_", - "_userauth.auth_fail", - "_userauth.sign_in", - "_userauth.sign_up", - "agentic", - "awslabs", - "@aws-amplify/auth", - "@aws-amplify/core", - "@aws-amplify/storage", "*.js", "0.75rem", "0.81rem", @@ -36,16 +22,28 @@ "7da1ff", "8.11.x", "8.x", + "@aws-amplify/auth", + "@aws-amplify/core", + "@aws-amplify/storage", + "_", + "_blank", + "_Granting", + "_Identity", + "_new", + "_or_", + "_userauth.auth_fail", + "_userauth.sign_in", + "_userauth.sign_up", "a_b", "aar", - "ABCDEXXXXX", "abcdefghi", + "ABCDEXXXXX", "abort", "AbortIncompleteMultipartUpload", "accesskey", "accessKey", - "ACCESSKEYID", "ACCESSKEY", + "ACCESSKEYID", "AccessToken", "AccountRepresentative", "ACLs", @@ -55,15 +53,18 @@ "addfunction", "addgraphqldatasource", "addingfunction", + "addingfunction", "adhere", + "adminui", + "agentic", + "AIRPLANETABLE", "albumVisit", - "ampx", "Alexa", "Algolia", "AllowedOrigin", "allowfullscreen", - "AllPosts.jsx", "allPosts", + "AllPosts.jsx", "allprojects", "ALPN", "AMAZON_COGNITO_USER_POOLS", @@ -74,40 +75,48 @@ "AmazonPersonalizeProvider", "AmazonS3Client", "Amplif", - "ampx", "amplifiedtodo", - "AMPLIFYCLI", + "Amplify", + "Amplify's", "amplify-CLI", - "amplifymapview", "amplify-provider-awscloudformation", "amplify-s3-album", "amplify-totp-setup", "amplify-vue", - "Amplify", - "Amplify's", - "amplify’s", "AmplifyAngularModule", - "amplifyapp.com", "amplifyapp", + "amplifyapp.com", "amplifybackend", - "amplifyhosting", - "amplifyconfiguration.json", + "AMPLIFYCLI", + "amplifyconfig", "amplifyconfiguration", + "amplifyconfiguration.json", "AmplifyEventBus", "amplifyframework", + "amplifyhosting", "amplifyjsapp", + "amplifyjsapp", + "AMPLIFYLAYERGUIDE", + "amplifymapview", "amplifyMeta.json", "AmplifyModules", "amplifyoutputs", "AmplifyPlugin", "amplifyPush", "amplifyrc", + "AMPLIFYRULES", "amplifyService", "AmplifyService", "AmplifyTheme.js", "amplifytools", - "amplifyxc.config", "amplifyxc", + "amplifyxc.config", + "amplify\u2019s", + "Ampligram", + "ampligram", + "ampx", + "ampx", + "ampx", "AMQJS", "analytics", "android.app.Activity", @@ -118,11 +127,12 @@ "angular-cli", "anonymized", "AnyPublisher", - "API_KEY", - "API.swift", - "API's", "api", + "API's", + "API.swift", + "API_KEY", "apigateway", + "apigatewayv", "apiId", "apiKey", "APIKeyExpirationEpoch", @@ -132,40 +142,45 @@ "apiwithkey", "APNs", "apolloapi", + "apolloapi", "apollographql", "apolloserver", - "app_id", + "apolloserver", "app.component.html", "app.component.ts", "app.js", "app.module.ts", "App.vue", + "app_id", "AppClientId", "AppClientIDWeb", "AppClientSecret", "appcompat", "APPDATA", + "AppDelegate", "AppDelegate.m", "AppDelegate.swift", - "AppDelegate", "appid", - "apigatewayv", "appleid", "appName.app", "AppStore", "Appsync", "AppSync", "AppSyncApiName", + "architected", "architecting", "Arial", "Arial", "arm64-v8a", - "armeabi-v7a", "armeabi", + "armeabi-v7a", "arn", "ARNs", "arraybuffer", "artifactID", + "ASIAVJKIAM", + "assetlinks", + "astro", "async", "asyncawait", "AsyncStorage", @@ -174,18 +189,16 @@ "Auth.currentAuthenticatedUser", "Auth.federatedSignIn", "Auth0", + "Authadmin", "Authauthenticated", - "Authunauthenticate", + "AUTHCONFIG", "authcurrentsession", - "authverifycurrentuserattribute", - "authverifycurrentuserattributesubmit", "authcurrentusercredentials", - "authgetpreferredmfa", - "authuserattributes", "authData", - "Authenticator's", "Authenticator", + "Authenticator's", "AuthenticatorActivity", + "authgetpreferredmfa", "AuthGuardService", "authMode", "authmycognitoresource", @@ -193,24 +206,33 @@ "authorizer", "Authorizer", "authorizers", + "authreadonly", "authState", "authtype", + "Authunauthenticate", + "authuserattributes", + "authverifycurrentuserattribute", + "authverifycurrentuserattributesubmit", "AuthZ", + "autobranch", + "autoclosure", + "autodetection", "autogenerated", + "autoKsuid", "autolinking", + "autologin", "Automerge", "autoplay", "autopopulate", "autopopulated", "autoscaling", "autotrack", - "aws_bots_config.name", - "AWS_IAM", + "aws", + "aws-amplify", "aws-amplify-angular", - "aws-amplify-react-native", "aws-amplify-react", + "aws-amplify-react-native", "aws-amplify-vue", - "aws-amplify", "aws-android-sdk-apigateway-core", "aws-android-sdk-auth-core", "aws-android-sdk-auth-facebook", @@ -222,9 +244,9 @@ "aws-android-sdk-cognitoauth", "aws-android-sdk-cognitoidentityprovider", "aws-android-sdk-core", + "aws-android-sdk-ddb", "aws-android-sdk-ddb-document", "aws-android-sdk-ddb-mapper", - "aws-android-sdk-ddb", "aws-android-sdk-ec2", "aws-android-sdk-elb", "aws-android-sdk-iot", @@ -249,8 +271,8 @@ "aws-mobile-appsync-sdk-ios", "aws-sdk-ios", "aws.cognito.signin.user.admin", - "aws", - "Authadmin", + "aws_bots_config.name", + "AWS_IAM", "AWSAPI", "AWSAPIGateway", "AWSAPIPlugin", @@ -260,6 +282,7 @@ "AWSAuthCore", "AWSAuthUI", "AWSAutoScaling", + "awscliv", "awscloudformation", "AWSCLOUDFORMATIONCONFIG", "AWSCLOUDFORMATIONCONFIG", @@ -271,8 +294,8 @@ "AWSComprehend", "awsconfig", "awsconfig", - "awsconfiguration.json", "AWSConfiguration", + "awsconfiguration.json", "AWSConnect", "AWSCore", "AWSDD", @@ -296,15 +319,16 @@ "AWSKinesisVideo", "AWSKinesisVideoArchivedMedia", "AWSKMS", + "awslabs", "AWSLambda", "AWSLex", "AWSLogs", "AWSMachineLearning", "awsmobile", + "AWSMobileClient", "AWSMobileClient.default", "AWSMobileClient.getInstance", "AWSMobileClient.sharedInstance", - "AWSMobileClient", "AWSOIDC", "AWSPinpoint", "AWSPinpointAnalyticsPlugin", @@ -312,15 +336,17 @@ "AWSRekognition", "AWSS", "AWSS3ServerSideEncryption", - "AWSS3TransferUtility.s3TransferUtility", "AWSS3TransferUtility", + "AWSS3TransferUtility.s3TransferUtility", "AWSSageMakerRuntime", + "AWSSDK", + "AWSSDKHTTP", "AWSSES", "AWSSimpleDB", "AWSSNS", "AWSSQS", - "AWSSTS", "AWSStaticCredentialsProvider", + "AWSSTS", "AWSTask", "AWSTextract", "AWSTranscribe", @@ -329,10 +355,10 @@ "AWSURL", "AWSUserPoolsSignIn", "Axios", - "backend-configs", - "backendstack", "backend", + "backend-configs", "backends", + "backendstack", "backgroundColor", "backgrounding", "backoff", @@ -354,8 +380,11 @@ "bestpractices", "betatest", "bidirectionality", + "bindui", + "birthdate", "birthdate", "Bitbucket", + "bitnami", "bizcorprole", "BizDev", "blacklist", @@ -363,14 +392,16 @@ "boilerplates", "boolean", "boto", + "boto", "botTitle", "BRANCHNAME", - "BROWSABLE", "Brandel", + "BROWSABLE", "bucketName", "build.gradle", "buildscript", "buildSync", + "buildui", "Bundler", "byCustomerByStatusByDate", "byname", @@ -381,15 +412,16 @@ "c4c4c4", "cacheable", "Cactuss", - "callout--info", "callout", "callout", + "callout--info", + "callouts", "camelCase", "canCancel", "cancelAllWithType", "cancelHandler", - "CannedAccessControlList.PublicRead", "CannedAccessControlList", + "CannedAccessControlList.PublicRead", "cantguessthis", "capacityInBytes", "Capitan", @@ -398,6 +430,7 @@ "CAPTCHAs", "Cartfile", "cfdoc", + "CHALLENGEANSWER", "ChallengeName", "chatbot", "Chatbot", @@ -409,43 +442,47 @@ "CHLG", "cicd", "CircleCI", + "classname", "classpath", "clearComplete", "cleartext", - "CLI's", "cli", + "CLI's", "Client.swift", "ClientId", "clientMetadata", "ClientMetadata", + "clijson", "cloudform", - "cloudformation-template", "CloudFormation", + "cloudformation-template", "Cloudfront", "CloudFront", "CloudWatch", + "cloudwatchlogs", + "CNAME", "CocoaLumberjack", "Cocoapods", "CocoaPods", "CodeCommit", "codegen", "CODEGENJOB", - "COGNITO_USERPOOL_ID", - "Cognito's", "cognito", "Cognito", "Cognito", + "Cognito's", + "COGNITO_USERPOOL_ID", "cognitoabcd", "cognitoauth", "cognitoauth", "CognitoIdentity", "cognitoidentityprovider", "CognitoUserPool", + "com.amazonaws", "com.amazonaws.mobile.auth.ui.SignInUI", "com.amazonaws.mobile.client.AWSMobileClient", "com.amazonaws.mobile.client.AWSStartupHandler", "com.amazonaws.mobile.client.AWSStartupResult", - "com.amazonaws", "com.myProjectName", "Comment.todo", "Compat", @@ -457,18 +494,21 @@ "configLevel", "configs", "configureaccess", + "confirmingsignincustomflow", "confirmSignIn", "ConfirmSignIn", "confirmSignInConfig", "confirmSignUp", "ConfirmSignUp", "confirmSignUpConfig", + "confirmsignupcustomflow", "connectedform", "connectionWithKeyExamples.md", "constraintlayout", "contactapi", "containertag", "continueWith", + "conver", "cordova", "CoreML", "Corp's", @@ -484,22 +524,27 @@ "CRUD", "CRUDL", "Cryptocurrency", + "Cryptocurrency", + "cryptofunction", "cryptofunction", "CSRF", "css", "Ctrl-c", - "customauth", "CUSTOM_AUTH", + "customauth", + "customcf", "customerEmail", + "customerprofiles", "customizable", "customtabs", "customui", - "conver", "dabit", "dataaccess", "dataacess", + "databind", "databinding", "datalogconfig", + "datamodel", "dataset", "datasource", "DataSource", @@ -508,6 +553,8 @@ "DataStore", "dd3f5b", "deddd", + "dedup", + "Dedup", "deduped", "deeplink", "Deeplink", @@ -536,11 +583,15 @@ "developerpreviewjs", "devguide", "devpreview", + "Dexie", + "dexie", "dflt", "Didfinishlaunchingwithoptions", + "disambiguator", "displayMode", "displayOrder", "dists", + "Dockerizing", "DocSet", "DocSets", "Donef", @@ -548,6 +599,8 @@ "dotenvx", "downcasting", "dropdown", + "duckdb", + "dynamicquery", "dynamoDB", "DynamoDB", "DynamoDBBillingMode", @@ -558,39 +611,51 @@ "e88b01", "echofunction", "ecommerce", + "editorgroupaccess", "Elasticsearch", "ElasticSearch", "ElasticsearchEBSVolumeGB", "ElasticsearchInstanceCount", "ElasticsearchInstanceType", "ElasticsearchStreamingFunctionName", - "electron.js", "electron", + "electron.js", "email_verified", "emailpassword", + "enabledMfas", "endraw", + "endregion", "enqueued", + "Entra", "entryComponent", "entryComponents", + "entrypoint", + "Entrypoint", "Entypo", "enum", + "enumlabel", + "enumtypid", "env", "ENVNAME", + "envs", + "ERESOLVE", + "Ersi", + "esac", "esarn", "escapehatch", "espaguetis", "event.prev.result", - "EventBus", "eventbridge", + "EventBus", + "eventhandling", "eventType", "eventValue", "execa", - "eventhandling", "execute", "expectedVersion", - "Entra", - "IDSAML", "FACEBOOK_TOKEN_HERE", + "Fargate", + "favoritefood", "fb1231231231232123123", "fbapi", "fbauth", @@ -600,38 +665,25 @@ "ff9900", "ffac31", "ffffff", - "Figma", - "Figma's", - "figma", - "fileuploader", - "architected", - "newsfeeds", - "textareas", - "figmatocode", - "databind", - "buildui", - "dynamicquery", - "uibuilder", - "staticbind", - "getcomponent", - "sqft", - "Ampligram", - "ampligram", - "classname", - "favoritefood", "fieldName", + "Fieldsdk", "Fieldzoh", "Fieldzwu", - "Fieldsdk", + "Figma", + "figma", + "Figma's", + "figmatocode", + "fileb", "filename", "Filepath", "Filepath", + "fileuploader", "findViewById", "firebaseconsole", "Firehose", + "Flexboxes", "FLIGHTSTATS", "flipside", - "Flexboxes", "forgotPassword", "ForgotPassword", "forgotPasswordConfig", @@ -639,6 +691,7 @@ "formapi", "formbuilder", "formfunction", + "formfunction", "formtable", "FORMTABLE", "frontend", @@ -647,20 +700,27 @@ "fullname", "Gapi", "ge", + "generatemodelsforlazyloadandcustomselectionset", "geo_point", "Geocode", + "Geocodes", "geofence", "Geofence", + "geofence", "Geofences", + "geofences", "Geofencing", "geolocation", "GEOMFROMTEXT", "geospatial", + "geospatial", "getAllKeys", "getApplicationContext", "getCacheCurSize", + "getcomponent", "getCredentials", "GETCURRENTUSER", + "GETCURRENTUSER", "getDeliveryMedium", "getIdentityId", "getIdToken", @@ -668,37 +728,42 @@ "getItem", "getitems", "getJWTToken", - "GetPost.jsx", + "getmytodofunction", "getPost", + "GetPost.jsx", "getTodo", "getTokens", "getUsername", - "getmytodofunction", + "gitflow", "github", "gitignore", "GitLab", "global.scss", - "gluegun's", "gluegun", + "gluegun's", "goapi", + "GOARCH", "google.gms", "googleusercontent", "gqlcompile", "gqlfunc", "gqlimages", + "gqlimages", + "gqllambda", "gqllambda", "gqls", + "gqls", + "gqlv", "gradle", - "gradlew", "Gradle", + "gradlew", "GraphiQL", "GRAPHQENDPOINT", - "graphql_endpoint_iam_region", "graphql", "Graphql", "GraphQL", - "GRAPHQLTRANSFORMER", "graphql#17-data-access-patterns", + "graphql_endpoint_iam_region", "graphqlapi", "GRAPHQLAPIENDPOINTOUTPUT", "GRAPHQLAPIIDOUTPUT", @@ -706,6 +771,8 @@ "graphqlconfig", "GRAPHQLENDPOINT", "GraphQLOperations", + "graphqls", + "GRAPHQLTRANSFORMER", "greetingfunction", "gridsome", "Gridsome", @@ -723,6 +790,7 @@ "HasMany", "headlessaccesskeyid", "headlesssecrectaccesskey", + "healthcheck", "Helvetica", "Helvetica", "Hoc", @@ -732,7 +800,9 @@ "home.page.ts", "HostedUI", "Hosting's", + "hotswap", "html", + "HTTPAPI", "https", "HTTPURL", "i.e.", @@ -745,23 +815,33 @@ "IdentityManager", "IDENTITYPOOL", "IdentityProviders.FACEBOOK.toString", + "identitystore", "IdP", "idpresponse", + "IDSAML", "IdToken", "iframe", + "imagedefinitions", "ImageView", + "immer", + "importauth", "IN_TRANSIT", "inappbrowser", + "inappbrowser", "inappmessaging", - "Iniciar", "index.html", "index.js", + "indexdef", "IndexedDB", + "indexname", "Info.plist", + "infowindow", + "Iniciar", "initapi", "inout", "inputs.mdx", "installable", + "instanceof", "Intelli", "interceptApplication", "Inventorys", @@ -776,6 +856,7 @@ "isSignedIn", "item.class.ts", "itemMaxSize", + "jamba", "javascript", "javax", "jcenter", @@ -797,13 +878,17 @@ "keystore", "kibana", "kill", - "kinesisfirehose", "Kinesis", + "kinesisfirehose", "KinesisFirehoseRecorder", "KinesisRecorder", "kinesisvideo", + "Kiro", + "kiro", "KMS-generated", + "knowledgebases", "Kotlin", + "kotlinx", "Kylo", "Lambd", "Lambda", @@ -813,11 +898,14 @@ "lateinit", "le", "libglib", + "Libre's", "libs", "libsecret", "lifecycle", "lifecycles", "lightgray", + "lightgray", + "linestring", "Linkification", "list.item.modal.html", "list.item.modal.ts", @@ -837,30 +925,39 @@ "Log.d", "Log.i", "Logcat", + "loggingconstraints", "logic_protocol_scheme", "LoginWithAmazon", "logoImage", + "longblob", + "longtext", "lt", "machinelearning", "macOS", "main.js", "main.ts", - "MainActivity.java", "MainActivity", + "MainActivity.java", "makeToast", "manageusers", "mangiare", + "manylinux", + "manytomany", "mapbox", "mapboxsdk", + "maplibre", "maplibregl", "master", "md", "mdash", "MediaAutoTrack", + "mediumblob", + "mediumint", + "mediumtext", + "menudetaileditors", "metadata", "mfaDescription", "mfaTypes", - "enabledMfas", "MiB", "middleware", "Millis", @@ -879,9 +976,6 @@ "mouseover", "mqttv", "msg", - "mybookapi", - "mysupersecurepassword", - "myusername", "Mult", "multenvtest", "multiauth", @@ -890,6 +984,7 @@ "Multifactor", "multipart", "multiPartConcurrencyLimit", + "multirepo", "multishells", "Mutation.createTodo", "mutationName", @@ -899,55 +994,66 @@ "myAmplifyProject", "myangularapp", "myapi", - "reactamplified", - "region", - "myapp.xcodeproj", "myapp", + "myapp.xcodeproj", "myapplication", + "myawesomekey", + "mybookapi", "mybucket", "mycognitoresource", "mycoolpassword", + "mydb", "mydev", "myendpoint", "myenvname", + "myfunction", "myimage", "mykey", "mylambda", - "mynextapp", "mynewpassword", + "mynextapp", + "mynextapp", "myoldpassword", - "myPrivatePrefix,", "myprivateprefix", - "myPublicPrefix,", + "myPrivatePrefix,", + "myproject", "mypublicprefix", + "myPublicPrefix,", "MYRESOURCENAME", "mysandbox", "mysecretproject", "mysecurerandompassword", "MySQL", "MYSTORAGE", + "MYSTORAGE", "mystream", + "mysupersecurepassword", "mytestproject", "mytrigger", - "netinfo", + "myusername", + "myvalue", "NATIVECLIENT", + "netinfo", "Neue", "Neue", "NEW_PASSWORD_REQUIRED", "newHire", "newimage", - "NextActivity.class", + "newsfeeds", "NextActivity", + "NextActivity.class", "nextActivityClass", + "nextamplified", + "nextamplifygen", "nextjs", "Nextjs", "nextsteps", "ng-recaptcha", "Nkjd", - "node_modules", - "Node.js.", "node.js", "Node.js", + "Node.js.", + "node_modules", "nodeapi", "NodeJS", "nodownload", @@ -955,6 +1061,7 @@ "noMfaDescription", "non-SRP", "Nonnull", + "norpc", "NoSQL", "NoteContent", "NoteId", @@ -962,9 +1069,10 @@ "notificationclick", "npm", "npx", - "nsswamin", "NSAllowsLocalNetworking", "NSData", + "nspname", + "nsswamin", "NSURL", "NSURLSession", "NSUUID", @@ -972,8 +1080,8 @@ "Nuxt", "oauth", "OAuth", - "OAuth2.0", "oauth2", + "OAuth2.0", "OBJC", "OBJECT_KEY", "observeQuery", @@ -985,15 +1093,17 @@ "onCreate", "onDelete", "OneGraph", + "onetomany", + "Onetoone", "onHubCapsule", "OnInit", "onPick", "onResult", "onUpdate", "onvalidate", - "OPENID_CONNECT", "openid", "OpenID", + "OPENID_CONNECT", "OpenSearch", "opensearchservice", "openssl", @@ -1003,22 +1113,27 @@ "Outputs.RootURL.Value", "ownerField", "ownerfields", + "ownersaccess", "p12", "paho", "Pancho", "parameter.json", "parameterization", "parameters.json", + "parametersjson", "parcelable", "passin", "passwordless", "pausable", "PAY_PER_REQUEST", "Permission.Read", + "Persistor", + "persistor", "phone_number", "phonenumber", "PhotoPicker", "piace", + "Pinia", "PinpointAnalytics", "pinpointappid", "PinpointFunctionsOutputs", @@ -1026,6 +1141,7 @@ "PinpointTargeting", "pipenv", "pipenv's", + "Pipfile", "PITR", "pkey", "placeindex", @@ -1046,15 +1162,18 @@ "postname", "posts.graphql", "PostsTable", + "posttags", "posttitle", "powertools", + "Powertools", + "Pre", "pre-annotated", "pre-built", "pre-created", "pre-defined", "pre-populated", "pre-signed", - "Pre", + "preconfigured", "prepend", "prepended", "prepper", @@ -1062,8 +1181,10 @@ "presigned", "Presigning", "PreSignUp", + "Pressable", "prev", "printLn", + "privatesaccess", "ProcessLifeCycle", "programmatically", "Project.team", @@ -1072,12 +1193,13 @@ "Provisioining", "proxied", "pseudocode", + "psql", "publicRead", "pubspec", + "pubspec", "pubsub", "Pubsub", "PubSub", - "Powertools", "PUSHINFOPROVIDER", "PushListenerService", "pushnotification", @@ -1090,22 +1212,23 @@ "putVocabularies", "pythonapi", "qafh", - "QLAPINONEDS", "QLAPI", + "QLAPINONEDS", "QLID", "qrcode", "Query.commentsForTodo", "Query.echo.req.vtl", "Query.echo.res.vtl", "Query.getComment", - "Query.getTodo.comments", "Query.getTodo", + "Query.getTodo.comments", "Query.listComments", "Query.listTodos", "Query.nearbyTodos", "Query.searchTodos", "queryField", "QueryField", + "querytransfers", "queueing", "quickstart", "Quokka", @@ -1120,25 +1243,34 @@ "R.layout.activity_authenticator", "react-google-recaptcha", "react-native-fs", + "reactamplified", "REACTCONFIG", "reactnative", "realtime", + "rebranded", "reCaptcha", "recordcache", "RecyclerView", - "retryable", "redirect_to", "referrerpolicy", "refetches", + "refreshable", + "region", "Registrarse", - "Regístrate", + "Reg\u00edstrate", "Rehydrated", + "rehype", "Rekognition", + "remoteconfig", + "remotelogging", + "remoteloggingconstraints", "removeItem", "repo", "reponame", + "reponame", "requireNewPassword", "resendSignUp", + "resettingpassword", "resolver's", "resourcename", "Resources.S3Bucket.Properties.BucketName", @@ -1146,26 +1278,28 @@ "RESTAPI", "RESTENDPOINT", "resubscription", + "retryable", "retryLimit", "returnValue", "rgba", "richard", "rnamplify", + "roadmap", "RoleName", + "ROOT_QUERY", "ROOT_QUERY.getPost", "ROOT_QUERY.listPosts", - "ROOT_QUERY", "rootstack", "Route53", "runOnUiThread", - "runtime's", "runtime", + "runtime's", "runtimes", "rxbindings", "RxJava", "S'inscrire", - "s3.amazonaws.com", "s3", + "s3.amazonaws.com", "s3aeaffb53", "S3Album", "s3AlbumConfig", @@ -1180,16 +1314,17 @@ "Salli", "SAML", "savedInstanceState", + "savegeofences", "sceneapi", + "schema's", "schema.graphql", "schema.json", - "schema's", "schemas", "screencap", "screencast", "scrollview", - "SDK's", "sdk", + "SDK's", "SDKs", "SDLC", "searchable", @@ -1197,9 +1332,10 @@ "searchQueryField", "secretkey", "secretKey", + "secretsmanager", "serverless", "ServiceWorker", - "Sesión", + "Sesi\u00f3n", "SessionStorage", "setContentView", "setItem", @@ -1213,49 +1349,59 @@ "signin", "signInChallengeResponse", "signInConfig", - "signInResult.getSignInState", - "signInResult.GetSignInState", + "signingup", "signInResult", "SignInResult", + "signInResult.getSignInState", + "signInResult.GetSignInState", "SignInUIConfiguration.builder", "SignInUIOptions.builder", "signout", "signOutButton", "signup", - "signUpConfig.signUpFields", "signUpConfig", + "signUpConfig.signUpFields", "signUpFields", - "signUpResult.getConfirmationState", - "signUpResult.getUserCodeDeliveryDetails", "signUpResult", "SignUpResult", + "signUpResult.getConfirmationState", + "signUpResult.getUserCodeDeliveryDetails", "signups", "sitekey", + "SIWA", "sjgub", "sjqub", "slave", - "SMSMFA", + "smallint", "SMS_MFA", "smsDescription", - "someuser", + "SMSMFA", + "snapcraft", + "snapshotted", + "snstopic", + "snstopicemailsub", "softwareupdate", + "someuser", "spinner.js", + "sqft", "SQLite", "src", "SRID", - "snstopic", - "snstopicemailsub", "SSECustomerAlgorithm", "SSECustomerKey", "SSECustomerKeyMD5", "SSEKMS", "ssn", + "startbuild", + "Stateful", "Stateful", + "staticbind", "status#createdAt", "statusCreatedAt", "Storage.get", "Storage.put", "storagebucketname", + "STORAGECONFIG", "storagedemo", "storageOptions", "storagepath", @@ -1280,6 +1426,7 @@ "super.onCreate", "superset", "suppressschemamigrationprompt", + "swiftpm", "SwiftUI", "syncable", "syncConfiguration", @@ -1293,25 +1440,30 @@ "tanstack", "task.result", "taskIdentifier", - "template.json", "TEAMID", + "template.json", "testappa", "testtable", + "testtable", "testtrigger", + "textareas", "textEnabled", "Textract", - "textView.setText", "textView", "TextView", + "textView.setText", "theListFunction", "theming", "theming", "timeoutIntervalForResource", "TimeWatched", + "tinyblob", + "tinyint", + "tinytext", "TMQTT", "todelete", - "Todo.comments", "todo", + "Todo.comments", "todoapp", "todoitem", "todoitems", @@ -1321,9 +1473,9 @@ "Todotable", "tokenInstructions", "toolchain", - "totp-setup", "totp", "TOTP", + "totp-setup", "totpDescription", "TransferNetworkConnectionType", "TransferService", @@ -1331,8 +1483,8 @@ "TransferThreadPoolSize", "transferUtility", "TransferUtility", - "TRANSFORMERVERSION", "transform.conf.json", + "TRANSFORMERVERSION", "transpile", "transpiled", "transpiles", @@ -1341,8 +1493,12 @@ "TTLs", "typeName", "typeof", + "typname", + "typnamespace", "UDID", "ui", + "UI's", + "uibuilder", "UIColor", "UINavigationController", "Uint", @@ -1351,6 +1507,7 @@ "unauth", "Unauth", "uncategorized", + "unclustered", "uncommenting", "unencrypted", "unioned", @@ -1364,6 +1521,7 @@ "unregisters", "unsanitized", "Unselect", + "unsynced", "Untag", "updateapi", "updateauth", @@ -1371,47 +1529,55 @@ "UpdateTable", "updateTodo", "uploadData", + "Uploader", + "uppercased", "upvotes", "URIs", - "URL's", "url", + "URL's", "urls", "use", - "userguide", "useamplify", "useamplifyabcd", "usecase", "user_identity_id", "userauth", "UserCodeDeliveryDetails", - "userfiles-mobilehub", "userfiles", + "userfiles-mobilehub", + "userguide", "userid", + "userids", "usernameAttributes", + "usernamne", "userpool", "userpool", "UserPool", + "USERPOOL", "UserPoolId", "userPools", "UserPools", "userState", "UserState", - "userStateDetails.getUserState", "userStateDetails", "UserStateDetails", + "userStateDetails.getUserState", "usersub", "USERTABLE", - "USERPOOL", "util", "v2.0", "v2.7", "v25.0.0", "validationData", "vanillajs", + "vararg", + "varbinary", "varchar", "vendedlogs", + "vercel", "verify.js", "VerifyAuthChallengeResponse", + "verifyingattributes", "VeriSign", "versioned", "versionField", @@ -1419,23 +1585,27 @@ "versionInput", "ViewController.swift", "viewmodel", + "visionos", "vite", "voiceEnabled", + "voteField", + "Vue", "vue-recaptcha", "Vue.js", - "Vue", + "wafv", "walk", "walkthrough", "warningThreshold", "webapp", - "webconsole", + "webauthn", "WEBCLIENT", + "webconsole", "webhook", + "webp", "webpack", "websocket", "WebSocket", "webview", - "webp", "whitelist", "widget", "widget's", @@ -1446,29 +1616,34 @@ "withAuthenticator", "withFederated", "withoauth", + "withoauth", + "WORKDIR", "X-Amz-Date", "X-Amz-Security-Token", "X-Api-Key", - "x86_64", "x86", + "x86_64", "Xabcdefghij", "xcconfig", - "Xcode's", "Xcode", + "Xcode's", "xcodeproj", + "xcshareddata", "xcworkspace", "xfbml", "xib", + "xmark", "xml", - "XR.start", "xr", - "XX-XXXX-X_abcd1234", + "XR.start", "XX-XXXX-X", + "XX-XXXX-X_abcd1234", "XXXXXXXX-XXXX-1234-abcd-1234567890ab", "xyz123useamplifyabcdClient.swift", "Yeezy", "yeezyboost", "Yeezys", + "Yeezys", "YOUR_API_RESOURCE_NAME", "YOUR_APP_NAME", "YOUR_PROJECT_FOLDER", @@ -1481,188 +1656,24 @@ "yourserver", "Zocial", "zoneinfo", - "bindui", - "birthdate", - "usernamne", - "formfunction", - "boto", - "AIRPLANETABLE", - "apolloapi", - "apolloserver", - "testtable", - "gqlimages", - "gqls", - "gqllambda", - "Cryptocurrency", - "cryptofunction", - "mynextapp", - "Yeezys", - "addingfunction", - "reponame", - "MYSTORAGE", - "amplifyjsapp", - "lightgray", - "inappbrowser", - "importauth", - "withoauth", - "myawesomekey", - "myvalue", - "есть", - "нравится", - "спагетти", - "signingup", - "confirmsignupcustomflow", - "confirmingsignincustomflow", - "verifyingattributes", - "resettingpassword", - "pubspec", - "Stateful", - "GETCURRENTUSER", - "amplifyconfig", - "nextamplified", - "vercel", - "Uploader", - "AMPLIFYLAYERGUIDE", - "instanceof", - "CHALLENGEANSWER", - "Fargate", - "Dockerizing", - "duckdb", - "WORKDIR", - "endregion", - "entrypoint", - "Entrypoint", - "healthcheck", - "CNAME", - "imagedefinitions", - "mydb", - "bitnami", - "adminui", - "autologin", - "UI's", - "ownersaccess", - "privatesaccess", - "menudetaileditors", - "editorgroupaccess", - "authreadonly", - "envs", - "Onetoone", - "onetomany", - "manytomany", - "datamodel", - "AUTHCONFIG", - "myproject", - "STORAGECONFIG", - "secretsmanager", - "customcf", - "Pipfile", - "maplibre", - "unclustered", - "geospatial", - "Ersi", - "Geocodes", - "gqlv", - "ASIAVJKIAM", - "Pressable", - "geofence", - "geofences", - "savegeofences", - "myfunction", - "roadmap", - "infowindow", - "Libre's", - "Pinia", - "kotlinx", - "snapcraft", - "snapshotted", - "visionos", - "loggingconstraints", - "remoteconfig", - "remoteloggingconstraints", - "remotelogging", - "cloudwatchlogs", - "userids", - "xmark", - "refreshable", - "querytransfers", - "generatemodelsforlazyloadandcustomselectionset", - "wafv", - "autoKsuid", - "astro", - "disambiguator", - "tinyint", - "tinytext", - "mediumtext", - "longtext", - "linestring", - "smallint", - "mediumint", - "varbinary", - "tinyblob", - "mediumblob", - "longblob", - "psql", - "indexname", - "indexdef", - "typname", - "enumlabel", - "enumtypid", - "typnamespace", - "nspname", - "autobranch", - "gitflow", - "SIWA", - "hotswap", - "clijson", - "parametersjson", - "nextamplifygen", - "multirepo", - "startbuild", - "awscliv", - "identitystore", - "esac", - "voteField", - "ampx", - "autodetection", - "jamba", - "webauthn", - "knowledgebases", - "rehype", - "assetlinks", - "AMPLIFYRULES", - "preconfigured", - "manylinux", - "GOARCH", - "norpc", - "AWSSDKHTTP", - "HTTPAPI", - "AWSSDK", - "uppercased", - "autoclosure", - "Kiro", - "kiro", - "Persistor", - "persistor", - "Dexie", - "dexie", - "dedup", - "Dedup", - "posttags", - "callouts", - "immer", - "ERESOLVE", - "graphqls", - "swiftpm", - "xcshareddata", - "unsynced", - "vararg" + "\u0435\u0441\u0442\u044c", + "\u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f", + "\u0441\u043f\u0430\u0433\u0435\u0442\u0442\u0438" + ], + "flagWords": [ + "hte", + "full-stack", + "Full-stack", + "Full-Stack", + "sudo" ], - "flagWords": ["hte", "full-stack", "Full-stack", "Full-Stack", "sudo"], "patterns": [ { "name": "youtube-embed-ids", "pattern": "/embedId=\".*\" /" } ], - "ignoreRegExpList": ["youtube-embed-ids"] + "ignoreRegExpList": [ + "youtube-embed-ids" + ] } diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index fe42d05a123..129b1dd3c58 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -482,6 +482,10 @@ export const directory = { { path: 'src/pages/[platform]/build-a-backend/add-aws-services/analytics/existing-resources/index.mdx', section: 'backend' + }, + { + path: 'src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx', + section: 'backend' } ] }, diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx index 26d03107acf..8bce0207040 100644 --- a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx +++ b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx @@ -18,6 +18,7 @@ export const getStaticPaths = async () => { export function getStaticProps(context) { return { props: { + platform: context.params.platform, meta } }; @@ -112,7 +113,7 @@ aws customer-profiles create-domain \ This tells Connect how to store device tokens on user profiles. - + ```bash aws customer-profiles put-profile-object-type \ @@ -166,7 +167,7 @@ cp.put_profile_object( Amazon Connect Journeys support email, SMS, WhatsApp, and voice as native channels — but **not push notifications**. To send pushes from a Journey, you need a Lambda function that bridges the gap. The Journey invokes this Lambda via a "Custom Action" block, and the Lambda reads device tokens from the user's profile objects and calls End User Messaging to deliver the push via FCM/APNs. - + Create an execution role with these permissions: @@ -214,7 +215,7 @@ Trust policy: - + The Lambda receives a batch of customer profiles from the Journey's Custom Action block. It reads each profile's device tokens and sends push notifications via End User Messaging. Notification content comes from environment variables so you can update it without redeploying code. From bca9f6459f289132cadc401dc2241e121f188888 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Mon, 1 Jun 2026 13:51:50 -0700 Subject: [PATCH 13/13] docs: convert all examples to TypeScript (AWS SDK v3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review feedback — Amplify backend is TypeScript-first. Converts: direct send, endpoint import, Lambda, identifyUser, and Flutter backend proxy from Python to TypeScript. --- .../analytics/pinpoint-migration/index.mdx | 332 +++++++++--------- 1 file changed, 172 insertions(+), 160 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx index 8bce0207040..b4781b7f2d2 100644 --- a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx +++ b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/pinpoint-migration/index.mdx @@ -50,38 +50,39 @@ If you already manage your own device token lists and just need to send pushes, -```python -import boto3 - -eum = boto3.client('pinpoint', region_name='us-east-1') -APP_ID = 'your-end-user-messaging-app-id' # Your existing Pinpoint Application ID - -devices = [ - {'token': 'fcm-token-abc123', 'channel': 'GCM'}, - {'token': 'apns-token-xyz789', 'channel': 'APNS'}, -] - -for device in devices: - response = eum.send_messages( - ApplicationId=APP_ID, - MessageRequest={ - 'Addresses': { - device['token']: {'ChannelType': device['channel']} - }, - 'MessageConfiguration': { - 'GCMMessage': { - 'Title': 'Your notification title', - 'Body': 'Your notification body', - 'Action': 'OPEN_APP', - }, - 'APNSMessage': { - 'Title': 'Your notification title', - 'Body': 'Your notification body', - 'Action': 'OPEN_APP', - } - } +```typescript +import { PinpointClient, SendMessagesCommand } from '@aws-sdk/client-pinpoint'; + +const client = new PinpointClient({ region: 'us-east-1' }); +const APP_ID = 'your-end-user-messaging-app-id'; // Your existing Pinpoint Application ID + +const devices = [ + { token: 'fcm-token-abc123', channel: 'GCM' as const }, + { token: 'apns-token-xyz789', channel: 'APNS' as const }, +]; + +for (const device of devices) { + await client.send(new SendMessagesCommand({ + ApplicationId: APP_ID, + MessageRequest: { + Addresses: { + [device.token]: { ChannelType: device.channel } + }, + MessageConfiguration: { + GCMMessage: { + Title: 'Your notification title', + Body: 'Your notification body', + Action: 'OPEN_APP', + }, + APNSMessage: { + Title: 'Your notification title', + Body: 'Your notification body', + Action: 'OPEN_APP', } - ) + } + } + })); +} ``` **Pros:** Minimal migration. No Connect setup. Keep your existing workflow. @@ -143,24 +144,24 @@ This creates an `AmplifyDevice` object type with fields for `deviceId`, `deviceT Follow the [official Pinpoint migration guide](https://docs.aws.amazon.com/pinpoint/latest/userguide/migrate.html#migration-steps) to export your endpoints, then import them as Customer Profiles. For push endpoints (GCM/APNS), also register them as device profile objects: -```python -import boto3, json - -cp = boto3.client('customer-profiles', region_name='us-east-1') -DOMAIN = 'my-push-domain' - -# For each push device from your Pinpoint export: -cp.put_profile_object( - DomainName=DOMAIN, - ObjectTypeName='AmplifyDevice', - Object=json.dumps({ - 'userId': 'user-001', - 'deviceId': 'pinpoint-endpoint-id', - 'deviceToken': 'fcm-or-apns-token', - 'channelType': 'GCM', - 'platform': 'Android', - }) -) +```typescript +import { CustomerProfilesClient, PutProfileObjectCommand } from '@aws-sdk/client-customer-profiles'; + +const client = new CustomerProfilesClient({ region: 'us-east-1' }); +const DOMAIN = 'my-push-domain'; + +// For each push device from your Pinpoint export: +await client.send(new PutProfileObjectCommand({ + DomainName: DOMAIN, + ObjectTypeName: 'AmplifyDevice', + Object: JSON.stringify({ + userId: 'user-001', + deviceId: 'pinpoint-endpoint-id', + deviceToken: 'fcm-or-apns-token', + channelType: 'GCM', + platform: 'Android', + }) +})); ``` #### Step 4: Deploy the push delivery Lambda @@ -219,82 +220,89 @@ Trust policy: The Lambda receives a batch of customer profiles from the Journey's Custom Action block. It reads each profile's device tokens and sends push notifications via End User Messaging. Notification content comes from environment variables so you can update it without redeploying code. -```python -# push_delivery_lambda.py -import boto3 -import json -import os - -cp = boto3.client('customer-profiles') -# Push delivery uses the mobiletargeting API (boto3.client('pinpoint')), -# which remains available after Pinpoint EOL. -eum = boto3.client('pinpoint') - -DOMAIN = os.environ['CUSTOMER_PROFILES_DOMAIN'] -EUM_APP_ID = os.environ['EUM_APP_ID'] -NOTIFICATION_TITLE = os.environ.get('NOTIFICATION_TITLE', 'Notification') -NOTIFICATION_BODY = os.environ.get('NOTIFICATION_BODY', '') - -def handler(event, context): - """ - Invoked by a Connect Journey Custom Action block. - Event format: https://docs.aws.amazon.com/connect/latest/adminguide/lambda-invoke-functions.html - """ - profiles = event['Items']['CustomerProfiles'] - response_items = [] - - for record in profiles: - profile_id = record['ProfileId'] - - # Get device tokens from profile objects - devices_response = cp.list_profile_objects( - DomainName=DOMAIN, - ObjectTypeName='AmplifyDevice', - ProfileId=profile_id - ) - - statuses = [] - for obj in devices_response.get('Items', []): - device = json.loads(obj['Object']) - token = device.get('deviceToken') - channel = device.get('channelType', 'GCM') - - if not token: - continue - - msg_config = {} - if channel == 'GCM': - msg_config['GCMMessage'] = { - 'Title': NOTIFICATION_TITLE, - 'Body': NOTIFICATION_BODY, - 'Action': 'OPEN_APP', - } - elif channel in ('APNS', 'APNS_SANDBOX'): - msg_config['APNSMessage'] = { - 'Title': NOTIFICATION_TITLE, - 'Body': NOTIFICATION_BODY, - 'Action': 'OPEN_APP', - } - - resp = eum.send_messages( - ApplicationId=EUM_APP_ID, - MessageRequest={ - 'Addresses': {token: {'ChannelType': channel}}, - 'MessageConfiguration': msg_config, - } - ) - - status = resp['MessageResponse']['Result'].get( - token, {} - ).get('DeliveryStatus', 'UNKNOWN') - statuses.append(status) - - response_items.append({ - 'Id': profile_id, - 'ResultData': {'deliveryStatuses': statuses} - }) - - return {'Items': {'CustomerProfiles': response_items}} +```typescript +// push-delivery/index.ts +import { CustomerProfilesClient, ListProfileObjectsCommand } from '@aws-sdk/client-customer-profiles'; +import { PinpointClient, SendMessagesCommand } from '@aws-sdk/client-pinpoint'; +import type { MessageConfiguration } from '@aws-sdk/client-pinpoint'; + +// Push delivery uses the mobiletargeting API (@aws-sdk/client-pinpoint), +// which remains available after Pinpoint EOL. +const cp = new CustomerProfilesClient({}); +const eum = new PinpointClient({}); + +const DOMAIN = process.env.CUSTOMER_PROFILES_DOMAIN!; +const EUM_APP_ID = process.env.EUM_APP_ID!; +const NOTIFICATION_TITLE = process.env.NOTIFICATION_TITLE ?? 'Notification'; +const NOTIFICATION_BODY = process.env.NOTIFICATION_BODY ?? ''; + +interface CampaignEvent { + Items: { + CustomerProfiles: Array<{ + ProfileId: string; + CustomerData: string; + IdempotencyToken: string; + }>; + }; +} + +/** + * Invoked by a Connect Journey Custom Action block. + * Event format: https://docs.aws.amazon.com/connect/latest/adminguide/lambda-invoke-functions.html + */ +export const handler = async (event: CampaignEvent) => { + const profiles = event.Items.CustomerProfiles; + const responseItems = []; + + for (const record of profiles) { + const { ProfileId } = record; + + const devicesResponse = await cp.send(new ListProfileObjectsCommand({ + DomainName: DOMAIN, + ObjectTypeName: 'AmplifyDevice', + ProfileId, + })); + + const statuses: string[] = []; + for (const obj of devicesResponse.Items ?? []) { + const device = JSON.parse(obj.Object!); + const token: string | undefined = device.deviceToken; + const channel: string = device.channelType ?? 'GCM'; + + if (!token) continue; + + const MessageConfiguration: MessageConfiguration = {}; + if (channel === 'GCM') { + MessageConfiguration.GCMMessage = { + Title: NOTIFICATION_TITLE, + Body: NOTIFICATION_BODY, + Action: 'OPEN_APP', + }; + } else if (channel === 'APNS' || channel === 'APNS_SANDBOX') { + MessageConfiguration.APNSMessage = { + Title: NOTIFICATION_TITLE, + Body: NOTIFICATION_BODY, + Action: 'OPEN_APP', + }; + } + + const resp = await eum.send(new SendMessagesCommand({ + ApplicationId: EUM_APP_ID, + MessageRequest: { + Addresses: { [token]: { ChannelType: channel } }, + MessageConfiguration, + }, + })); + + const status = resp.MessageResponse?.Result?.[token]?.DeliveryStatus ?? 'UNKNOWN'; + statuses.push(status); + } + + responseItems.push({ Id: ProfileId, ResultData: { deliveryStatuses: statuses } }); + } + + return { Items: { CustomerProfiles: responseItems } }; +}; ``` @@ -302,12 +310,15 @@ def handler(event, context): ##### Deploy the Lambda ```bash -zip lambda.zip push_delivery_lambda.py +# Bundle and deploy (using esbuild) +npx esbuild push-delivery/index.ts --bundle --platform=node --outfile=dist/index.js + +cd dist && zip -r ../lambda.zip index.js && cd .. aws lambda create-function \ --function-name push-delivery \ - --runtime python3.12 \ - --handler push_delivery_lambda.handler \ + --runtime nodejs20.x \ + --handler index.handler \ --role arn:aws:iam::ACCOUNT:role/push-delivery-role \ --zip-file fileb://lambda.zip \ --environment 'Variables={CUSTOMER_PROFILES_DOMAIN=my-push-domain,EUM_APP_ID=your-end-user-messaging-app-id,NOTIFICATION_TITLE=Your Title,NOTIFICATION_BODY=Your message body}' @@ -351,19 +362,19 @@ Amplify's `identifyUser` API mapped to Pinpoint's `UpdateEndpoint`. The replacem | `plan` (custom attribute) | `Attributes.plan` | | Custom user attributes | `Attributes` map | -```python -import boto3 +```typescript +import { CustomerProfilesClient, CreateProfileCommand } from '@aws-sdk/client-customer-profiles'; -cp = boto3.client('customer-profiles', region_name='us-east-1') +const client = new CustomerProfilesClient({ region: 'us-east-1' }); -# Equivalent of identifyUser({ userId: 'user-001', name: 'Jane Doe', email: '...' }) -cp.create_profile( - DomainName='my-push-domain', - FirstName='Jane', - LastName='Doe', - EmailAddress='jane@example.com', - Attributes={'plan': 'premium', 'locale': 'en_US'} -) +// Equivalent of identifyUser({ userId: 'user-001', name: 'Jane Doe', email: '...' }) +await client.send(new CreateProfileCommand({ + DomainName: 'my-push-domain', + FirstName: 'Jane', + LastName: 'Doe', + EmailAddress: 'jane@example.com', + Attributes: { plan: 'premium', locale: 'en_US' } +})); ``` ## Registering devices from your mobile app @@ -431,28 +442,29 @@ func registerDevice(userId: String, token: String, deviceId: String) async throw -```dart +```typescript // Flutter apps should call a backend endpoint that proxies to Customer Profiles. -// Example backend Lambda (Python): - -import boto3, json - -cp = boto3.client('customer-profiles', region_name='us-east-1') - -def handler(event, context): - body = json.loads(event['body']) - cp.put_profile_object( - DomainName='my-push-domain', - ObjectTypeName='AmplifyDevice', - Object=json.dumps({ - 'userId': body['userId'], - 'deviceId': body['deviceId'], - 'deviceToken': body['deviceToken'], - 'channelType': body['channelType'], - 'platform': body['platform'], - }) - ) - return {'statusCode': 200} +// Example backend Lambda (TypeScript): +import { CustomerProfilesClient, PutProfileObjectCommand } from '@aws-sdk/client-customer-profiles'; +import type { APIGatewayProxyHandler } from 'aws-lambda'; + +const client = new CustomerProfilesClient({}); + +export const handler: APIGatewayProxyHandler = async (event) => { + const body = JSON.parse(event.body!); + await client.send(new PutProfileObjectCommand({ + DomainName: 'my-push-domain', + ObjectTypeName: 'AmplifyDevice', + Object: JSON.stringify({ + userId: body.userId, + deviceId: body.deviceId, + deviceToken: body.deviceToken, + channelType: body.channelType, + platform: body.platform, + }) + })); + return { statusCode: 200, body: '' }; +}; ``` ```dart