From 55f8c6152ca5b2b59f2c90ad02f87459e8c2a4fb Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:23:12 +0000 Subject: [PATCH 01/21] Update package.json --- .../discount-expiry-notifier/package.json | 1 + pnpm-lock.yaml | 21 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/handlers/discount-expiry-notifier/package.json b/handlers/discount-expiry-notifier/package.json index a38afcb7f0..20e38674ca 100644 --- a/handlers/discount-expiry-notifier/package.json +++ b/handlers/discount-expiry-notifier/package.json @@ -17,6 +17,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "3.735.0", + "@aws-sdk/client-sqs": "3.734.0", "@google-cloud/bigquery": "^7.9.1", "aws-sdk": "^2.1692.0", "google-auth-library": "^9.15.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 584f299820..29ec15ea19 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,6 +115,9 @@ importers: '@aws-sdk/client-s3': specifier: 3.735.0 version: 3.735.0 + '@aws-sdk/client-sqs': + specifier: 3.734.0 + version: 3.734.0 '@google-cloud/bigquery': specifier: ^7.9.1 version: 7.9.1 @@ -5090,32 +5093,32 @@ snapshots: '@aws-sdk/util-user-agent-browser': 3.734.0 '@aws-sdk/util-user-agent-node': 3.734.0 '@smithy/config-resolver': 4.0.1 - '@smithy/core': 3.1.1 + '@smithy/core': 3.1.2 '@smithy/fetch-http-handler': 5.0.1 '@smithy/hash-node': 4.0.1 '@smithy/invalid-dependency': 4.0.1 '@smithy/md5-js': 4.0.1 '@smithy/middleware-content-length': 4.0.1 - '@smithy/middleware-endpoint': 4.0.2 - '@smithy/middleware-retry': 4.0.3 - '@smithy/middleware-serde': 4.0.1 + '@smithy/middleware-endpoint': 4.0.3 + '@smithy/middleware-retry': 4.0.4 + '@smithy/middleware-serde': 4.0.2 '@smithy/middleware-stack': 4.0.1 '@smithy/node-config-provider': 4.0.1 '@smithy/node-http-handler': 4.0.2 '@smithy/protocol-http': 5.0.1 - '@smithy/smithy-client': 4.1.2 + '@smithy/smithy-client': 4.1.3 '@smithy/types': 4.1.0 '@smithy/url-parser': 4.0.1 '@smithy/util-base64': 4.0.0 '@smithy/util-body-length-browser': 4.0.0 '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.3 - '@smithy/util-defaults-mode-node': 4.0.3 + '@smithy/util-defaults-mode-browser': 4.0.4 + '@smithy/util-defaults-mode-node': 4.0.4 '@smithy/util-endpoints': 3.0.1 '@smithy/util-middleware': 4.0.1 '@smithy/util-retry': 4.0.1 '@smithy/util-utf8': 4.0.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -5825,7 +5828,7 @@ snapshots: '@aws-sdk/types@3.734.0': dependencies: '@smithy/types': 4.1.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-arn-parser@3.723.0': dependencies: From c6b3044ce9189aec9cf8e3ace2da7d31cb62eafc Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:23:14 +0000 Subject: [PATCH 02/21] Create CustomError.ts --- .../src/CustomError.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 handlers/discount-expiry-notifier/src/CustomError.ts diff --git a/handlers/discount-expiry-notifier/src/CustomError.ts b/handlers/discount-expiry-notifier/src/CustomError.ts new file mode 100644 index 0000000000..3ccd80ae6d --- /dev/null +++ b/handlers/discount-expiry-notifier/src/CustomError.ts @@ -0,0 +1,44 @@ +import type { EmailMessageWithUserId } from '@modules/email/email'; + +export class CustomError extends Error { + subName: string; + request: EmailMessageWithUserId; + response: string; + + constructor( + subName: string, + request: EmailMessageWithUserId, + response: string, + ) { + // Call the parent constructor with a custom message + super( + `Error in ${subName}: Request - ${JSON.stringify(request)}, Response - ${response}`, + ); + + // Set the prototype explicitly (necessary for proper instanceof checks in some environments) + Object.setPrototypeOf(this, CustomError.prototype); + + // Assign the custom properties + this.subName = subName; + this.request = request; + this.response = response; + + // Set the error name for better identification + this.name = 'CustomError'; + } +} + +// Example usage +// try { +// throw new CustomError('SubscriptionService', 'GET /subscriptions', '404 Not Found'); +// } catch (error) { +// if (error instanceof CustomError) { +// console.error(error.name); // CustomError +// console.error(error.message); // Error in SubscriptionService: Request - GET /subscriptions, Response - 404 Not Found +// console.error(error.subName); // SubscriptionService +// console.error(error.request); // GET /subscriptions +// console.error(error.response); // 404 Not Found +// } else { +// console.error(error); +// } +// } From 1a241265a4757bc9d15b6cbe989823c1ad0f6942 Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:23:23 +0000 Subject: [PATCH 03/21] Update bigquery.ts --- .../discount-expiry-notifier/src/bigquery.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/handlers/discount-expiry-notifier/src/bigquery.ts b/handlers/discount-expiry-notifier/src/bigquery.ts index bcfbdb336f..8beb01762a 100644 --- a/handlers/discount-expiry-notifier/src/bigquery.ts +++ b/handlers/discount-expiry-notifier/src/bigquery.ts @@ -73,14 +73,25 @@ export const runQuery = async ( return [ { firstName: 'David', - nextPaymentDate: '2025-02-28', - paymentAmount: 12, + nextPaymentDate: '2025-02-27', + paymentAmount: 1.23, paymentCurrency: 'GBP', paymentFrequency: 'Month', productName: 'Supporter Plus', sfContactId: '0039E00001HiIGlQAN', - subName: 'A-S00814342', // Active sub in dev sandbox - workEmail: 'david.pepper@guardian.co.uk', + subName: 'A-S00886188', // Active sub in dev sandbox + workEmail: 'david.pepper+david@guardian.co.uk', + }, + { + firstName: 'Mary', + nextPaymentDate: '2025-02-29', + paymentAmount: 4.56, + paymentCurrency: 'USD', + paymentFrequency: 'Month', + productName: 'Supporter Plus', + sfContactId: '0039E00001HiIGlQAN', + subName: 'A-S00814343', // Active sub in dev sandbox + workEmail: 'david.pepper+mary@guardian.co.uk', }, ]; }; From f050eb988b3d6be7c1f3f16711492ab77c475888 Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:23:25 +0000 Subject: [PATCH 04/21] Update initiateEmailSend.ts --- .../src/handlers/initiateEmailSend.ts | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts b/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts index 3ea0374b4e..da5495af90 100644 --- a/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts +++ b/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts @@ -1,5 +1,12 @@ -import { DataExtensionNames, sendEmail } from '@modules/email/email'; +import type { SendMessageCommandOutput } from '@aws-sdk/client-sqs'; +import { SendMessageCommand, SQSClient } from '@aws-sdk/client-sqs'; +import { awsConfig } from '@modules/aws/config'; +import { DataExtensionNames } from '@modules/email/email'; +import type { EmailMessageWithUserId } from '@modules/email/email'; +import { prettyPrint } from '@modules/prettyPrint'; import { stageFromEnvironment } from '@modules/stage'; +import type { Stage } from '@modules/stage'; +import { CustomError } from '../CustomError'; export const handler = async (event: { firstName: string; @@ -17,10 +24,10 @@ export const handler = async (event: { const payload = { ...{ To: { - Address: 'david.pepper@guardian.co.uk', // hard code this during testing + Address: event.workEmail, // hard code this during testing ContactAttributes: { SubscriberAttributes: { - EmailAddress: 'david.pepper@guardian.co.uk', // hard code this during testing + EmailAddress: event.workEmail, // hard code this during testing paymentAmount: `${currencySymbol}${event.paymentAmount}`, first_name: event.firstName, date_of_payment: formatDate(event.nextPaymentDate), @@ -33,12 +40,23 @@ export const handler = async (event: { }, SfContactId: event.sfContactId, }; + try { + const emailSend = await sendEmail(stageFromEnvironment(), payload); - const emailSend = await sendEmail(stageFromEnvironment(), payload); + console.log('emailSend:', emailSend); - console.log('emailSend:', emailSend); + return emailSend; + } catch (error) { + // throw new Error( + // `Error initiating email send in membership workflow: ${JSON.stringify(error)}`, - return emailSend; + // ); + throw new CustomError( + 'SubscriptionService', + payload, + JSON.stringify(error), + ); + } }; function formatDate(inputDate: string): string { @@ -60,3 +78,23 @@ function getCurrencySymbol(currencyCode: string): string { }; return symbols[currencyCode] ?? ''; } + +export const sendEmail = async ( + stage: Stage, + emailMessage: EmailMessageWithUserId, + log: (messsage: string) => void = console.log, +): Promise => { + const queueName = `braze-emails-${stage}-'blah`; + const client = new SQSClient(awsConfig); + log( + `Sending email message ${prettyPrint(emailMessage)} to queue ${queueName}`, + ); + const command = new SendMessageCommand({ + QueueUrl: queueName, + MessageBody: JSON.stringify(emailMessage), + }); + + const response = await client.send(command); + log(`Response from email send was ${prettyPrint(response)}`); + return response; +}; From 5583238ed93a3c48d265683b6f34efdc60ccf7a8 Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:24:43 +0000 Subject: [PATCH 05/21] Update bigquery.ts --- handlers/discount-expiry-notifier/src/bigquery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/discount-expiry-notifier/src/bigquery.ts b/handlers/discount-expiry-notifier/src/bigquery.ts index 8beb01762a..78082a0b01 100644 --- a/handlers/discount-expiry-notifier/src/bigquery.ts +++ b/handlers/discount-expiry-notifier/src/bigquery.ts @@ -90,7 +90,7 @@ export const runQuery = async ( paymentFrequency: 'Month', productName: 'Supporter Plus', sfContactId: '0039E00001HiIGlQAN', - subName: 'A-S00814343', // Active sub in dev sandbox + subName: 'A-S00814343', // Active subscription in dev sandbox workEmail: 'david.pepper+mary@guardian.co.uk', }, ]; From d8691f6e3906caf671bfdfbc9e226a588b511a2e Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:45:49 +0000 Subject: [PATCH 06/21] Update bigquery.ts --- handlers/discount-expiry-notifier/src/bigquery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/discount-expiry-notifier/src/bigquery.ts b/handlers/discount-expiry-notifier/src/bigquery.ts index 78082a0b01..0bc92bb89b 100644 --- a/handlers/discount-expiry-notifier/src/bigquery.ts +++ b/handlers/discount-expiry-notifier/src/bigquery.ts @@ -90,7 +90,7 @@ export const runQuery = async ( paymentFrequency: 'Month', productName: 'Supporter Plus', sfContactId: '0039E00001HiIGlQAN', - subName: 'A-S00814343', // Active subscription in dev sandbox + subName: 'A-S00886159', // Active subscription in dev sandbox workEmail: 'david.pepper+mary@guardian.co.uk', }, ]; From 51390460d9bc460e38f91c96e4b54b09bee075e4 Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:38:55 +0000 Subject: [PATCH 07/21] Delete CustomError.ts --- .../src/CustomError.ts | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 handlers/discount-expiry-notifier/src/CustomError.ts diff --git a/handlers/discount-expiry-notifier/src/CustomError.ts b/handlers/discount-expiry-notifier/src/CustomError.ts deleted file mode 100644 index 3ccd80ae6d..0000000000 --- a/handlers/discount-expiry-notifier/src/CustomError.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { EmailMessageWithUserId } from '@modules/email/email'; - -export class CustomError extends Error { - subName: string; - request: EmailMessageWithUserId; - response: string; - - constructor( - subName: string, - request: EmailMessageWithUserId, - response: string, - ) { - // Call the parent constructor with a custom message - super( - `Error in ${subName}: Request - ${JSON.stringify(request)}, Response - ${response}`, - ); - - // Set the prototype explicitly (necessary for proper instanceof checks in some environments) - Object.setPrototypeOf(this, CustomError.prototype); - - // Assign the custom properties - this.subName = subName; - this.request = request; - this.response = response; - - // Set the error name for better identification - this.name = 'CustomError'; - } -} - -// Example usage -// try { -// throw new CustomError('SubscriptionService', 'GET /subscriptions', '404 Not Found'); -// } catch (error) { -// if (error instanceof CustomError) { -// console.error(error.name); // CustomError -// console.error(error.message); // Error in SubscriptionService: Request - GET /subscriptions, Response - 404 Not Found -// console.error(error.subName); // SubscriptionService -// console.error(error.request); // GET /subscriptions -// console.error(error.response); // 404 Not Found -// } else { -// console.error(error); -// } -// } From 4fede36200b618c8f65b14214a3df1480090c58a Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:39:00 +0000 Subject: [PATCH 08/21] Update initiateEmailSend.ts --- .../src/handlers/initiateEmailSend.ts | 63 ++++++------------- 1 file changed, 20 insertions(+), 43 deletions(-) diff --git a/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts b/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts index da5495af90..fa8ba51007 100644 --- a/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts +++ b/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts @@ -1,12 +1,5 @@ -import type { SendMessageCommandOutput } from '@aws-sdk/client-sqs'; -import { SendMessageCommand, SQSClient } from '@aws-sdk/client-sqs'; -import { awsConfig } from '@modules/aws/config'; -import { DataExtensionNames } from '@modules/email/email'; -import type { EmailMessageWithUserId } from '@modules/email/email'; -import { prettyPrint } from '@modules/prettyPrint'; +import { DataExtensionNames, sendEmail } from '@modules/email/email'; import { stageFromEnvironment } from '@modules/stage'; -import type { Stage } from '@modules/stage'; -import { CustomError } from '../CustomError'; export const handler = async (event: { firstName: string; @@ -24,10 +17,10 @@ export const handler = async (event: { const payload = { ...{ To: { - Address: event.workEmail, // hard code this during testing + Address: event.workEmail, ContactAttributes: { SubscriberAttributes: { - EmailAddress: event.workEmail, // hard code this during testing + EmailAddress: event.workEmail, paymentAmount: `${currencySymbol}${event.paymentAmount}`, first_name: event.firstName, date_of_payment: formatDate(event.nextPaymentDate), @@ -41,21 +34,25 @@ export const handler = async (event: { SfContactId: event.sfContactId, }; try { - const emailSend = await sendEmail(stageFromEnvironment(), payload); + const response = await sendEmail(stageFromEnvironment(), payload); - console.log('emailSend:', emailSend); - - return emailSend; + return { + detail: event, + emailSendAttempt: { + status: 'success', + payload, + response + }, + }; } catch (error) { - // throw new Error( - // `Error initiating email send in membership workflow: ${JSON.stringify(error)}`, - - // ); - throw new CustomError( - 'SubscriptionService', - payload, - JSON.stringify(error), - ); + return { + detail: event, + emailSendAttempt: { + status: 'error', + payload, + response: error as string, + }, + }; } }; @@ -78,23 +75,3 @@ function getCurrencySymbol(currencyCode: string): string { }; return symbols[currencyCode] ?? ''; } - -export const sendEmail = async ( - stage: Stage, - emailMessage: EmailMessageWithUserId, - log: (messsage: string) => void = console.log, -): Promise => { - const queueName = `braze-emails-${stage}-'blah`; - const client = new SQSClient(awsConfig); - log( - `Sending email message ${prettyPrint(emailMessage)} to queue ${queueName}`, - ); - const command = new SendMessageCommand({ - QueueUrl: queueName, - MessageBody: JSON.stringify(emailMessage), - }); - - const response = await client.send(command); - log(`Response from email send was ${prettyPrint(response)}`); - return response; -}; From 1708e8417031884f0c9993750e589f1c9cf8d90c Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:39:43 +0000 Subject: [PATCH 09/21] no results needed for testing --- .../src/handlers/getSubsWithExpiringDiscounts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/discount-expiry-notifier/src/handlers/getSubsWithExpiringDiscounts.ts b/handlers/discount-expiry-notifier/src/handlers/getSubsWithExpiringDiscounts.ts index 98d6911225..308c344e71 100644 --- a/handlers/discount-expiry-notifier/src/handlers/getSubsWithExpiringDiscounts.ts +++ b/handlers/discount-expiry-notifier/src/handlers/getSubsWithExpiringDiscounts.ts @@ -70,7 +70,7 @@ const getQuery = (discountExpiresOnDate: string): string => sub.is_latest_version = TRUE AND sub.status = 'Active' AND DATE_ADD(charge.effective_start_date, INTERVAL charge.up_to_periods MONTH) = '${discountExpiresOnDate}' AND - sub.name = 'A-S02287430' + sub.name = 'xxxxx' ) SELECT STRING_AGG(DISTINCT firstName) as firstName, From 0a0d011653155972b0f6bdb28924ff5eb9fdb353 Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:44:48 +0000 Subject: [PATCH 10/21] formatting --- .../discount-expiry-notifier/src/handlers/initiateEmailSend.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts b/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts index fa8ba51007..cdc668c586 100644 --- a/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts +++ b/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts @@ -41,7 +41,7 @@ export const handler = async (event: { emailSendAttempt: { status: 'success', payload, - response + response, }, }; } catch (error) { From d193a1792d9ce60052146d570e4e7f2643709c16 Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:45:23 +0000 Subject: [PATCH 11/21] add distinct second test email --- handlers/discount-expiry-notifier/src/bigquery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/discount-expiry-notifier/src/bigquery.ts b/handlers/discount-expiry-notifier/src/bigquery.ts index 0bc92bb89b..5a597d5a87 100644 --- a/handlers/discount-expiry-notifier/src/bigquery.ts +++ b/handlers/discount-expiry-notifier/src/bigquery.ts @@ -91,7 +91,7 @@ export const runQuery = async ( productName: 'Supporter Plus', sfContactId: '0039E00001HiIGlQAN', subName: 'A-S00886159', // Active subscription in dev sandbox - workEmail: 'david.pepper+mary@guardian.co.uk', + workEmail: 'graham.hopgood@guardian.co.uk', }, ]; }; From 0a43ce9afcf12b03da34ce2d64b20d3bc8c5c346 Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:45:45 +0000 Subject: [PATCH 12/21] add sf sub id --- handlers/discount-expiry-notifier/src/bigquery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/discount-expiry-notifier/src/bigquery.ts b/handlers/discount-expiry-notifier/src/bigquery.ts index 5a597d5a87..1564022858 100644 --- a/handlers/discount-expiry-notifier/src/bigquery.ts +++ b/handlers/discount-expiry-notifier/src/bigquery.ts @@ -89,7 +89,7 @@ export const runQuery = async ( paymentCurrency: 'USD', paymentFrequency: 'Month', productName: 'Supporter Plus', - sfContactId: '0039E00001HiIGlQAN', + sfContactId: '0039E00001HwhM9QAJ', subName: 'A-S00886159', // Active subscription in dev sandbox workEmail: 'graham.hopgood@guardian.co.uk', }, From cab4c8ff9e667a1f0f8198f958141889e4ad8d66 Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 14:09:13 +0000 Subject: [PATCH 13/21] use state machine to separate successes from failures --- .../discount-expiry-notifier.test.ts.snap | 8 ++--- cdk/lib/discount-expiry-notifier.ts | 13 ++++++++ .../src/handlers/initiateEmailSend.ts | 33 +++++++++++++++++-- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap b/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap index 8f6f894371..8c0c0127e3 100644 --- a/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap +++ b/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap @@ -177,7 +177,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 1`] = ` "Arn", ], }, - "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"Save results","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"Next":"Is Subscription Active?","Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"SegmentResults","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"Next":"Is Subscription Active?","Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, @@ -199,7 +199,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 1`] = ` "Arn", ], }, - "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"SegmentResults":{"Type":"Pass","ResultPath":"$.segmentedResults","Parameters":{"discountExpiresOnDate.$":"$.discountExpiresOnDate","expiringDiscountsToProcess.$":"$.expiringDiscountsToProcess","successes.$":"States.ArrayFilter($.discountProcessingAttempts, $.emailSendAttempt.status == \\"success\\")","failures.$":"States.ArrayFilter($.discountProcessingAttempts, $.emailSendAttempt.status == \\"error\\")"},"Next":"Save results"},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, @@ -1534,7 +1534,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 2`] = ` "Arn", ], }, - "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"Save results","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"Next":"Is Subscription Active?","Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"SegmentResults","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"Next":"Is Subscription Active?","Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, @@ -1556,7 +1556,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 2`] = ` "Arn", ], }, - "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"SegmentResults":{"Type":"Pass","ResultPath":"$.segmentedResults","Parameters":{"discountExpiresOnDate.$":"$.discountExpiresOnDate","expiringDiscountsToProcess.$":"$.expiringDiscountsToProcess","successes.$":"States.ArrayFilter($.discountProcessingAttempts, $.emailSendAttempt.status == \\"success\\")","failures.$":"States.ArrayFilter($.discountProcessingAttempts, $.emailSendAttempt.status == \\"error\\")"},"Next":"Save results"},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, diff --git a/cdk/lib/discount-expiry-notifier.ts b/cdk/lib/discount-expiry-notifier.ts index cd347b861d..91949a946e 100644 --- a/cdk/lib/discount-expiry-notifier.ts +++ b/cdk/lib/discount-expiry-notifier.ts @@ -183,6 +183,18 @@ export class DiscountExpiryNotifier extends GuStack { const isSubActiveChoice = new Choice(this, 'Is Subscription Active?'); + const segmentResults = new Pass(this, 'SegmentResults', { + parameters: { + 'discountExpiresOnDate.$': '$.discountExpiresOnDate', + 'expiringDiscountsToProcess.$': '$.expiringDiscountsToProcess', + 'successes.$': + 'States.ArrayFilter($.discountProcessingAttempts, $.emailSendAttempt.status == "success")', + 'failures.$': + 'States.ArrayFilter($.discountProcessingAttempts, $.emailSendAttempt.status == "error")', + }, + resultPath: '$.segmentedResults', + }); + emailSendsProcessingMap.iterator( subIsActiveLambdaTask.next( isSubActiveChoice @@ -199,6 +211,7 @@ export class DiscountExpiryNotifier extends GuStack { const definitionBody = DefinitionBody.fromChainable( getSubsWithExpiringDiscountsLambdaTask .next(emailSendsProcessingMap) + .next(segmentResults) .next(saveResultsLambdaTask), ); diff --git a/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts b/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts index cdc668c586..bc2b983163 100644 --- a/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts +++ b/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts @@ -1,5 +1,11 @@ -import { DataExtensionNames, sendEmail } from '@modules/email/email'; +import type { SendMessageCommandOutput } from '@aws-sdk/client-sqs'; +import { SendMessageCommand, SQSClient } from '@aws-sdk/client-sqs'; +import { awsConfig } from '@modules/aws/config'; +import { DataExtensionNames } from '@modules/email/email'; +import type { EmailMessageWithUserId } from '@modules/email/email'; +import { prettyPrint } from '@modules/prettyPrint'; import { stageFromEnvironment } from '@modules/stage'; +import type { Stage } from '@modules/stage'; export const handler = async (event: { firstName: string; @@ -34,7 +40,8 @@ export const handler = async (event: { SfContactId: event.sfContactId, }; try { - const response = await sendEmail(stageFromEnvironment(), payload); + const succeed = event.subName === 'A-S00886188'; + const response = await sendEmail(stageFromEnvironment(), payload, succeed); return { detail: event, @@ -56,6 +63,28 @@ export const handler = async (event: { } }; +export const sendEmail = async ( + stage: Stage, + emailMessage: EmailMessageWithUserId, + succeed: boolean, + log: (messsage: string) => void = console.log, +): Promise => { + const queueName = succeed + ? `braze-emails-${stage}` + : `braze-emails-${stage}-blah`; + const client = new SQSClient(awsConfig); + log( + `Sending email message ${prettyPrint(emailMessage)} to queue ${queueName}`, + ); + const command = new SendMessageCommand({ + QueueUrl: queueName, + MessageBody: JSON.stringify(emailMessage), + }); + + const response = await client.send(command); + log(`Response from email send was ${prettyPrint(response)}`); + return response; +}; function formatDate(inputDate: string): string { return new Date(inputDate).toLocaleDateString('en-GB', { day: '2-digit', From ff6fec7ba0f8e0a2a01afb84910199eac64a608d Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 14:27:02 +0000 Subject: [PATCH 14/21] fix --- cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap | 4 ++-- cdk/lib/discount-expiry-notifier.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap b/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap index 8c0c0127e3..790099e14e 100644 --- a/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap +++ b/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap @@ -199,7 +199,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 1`] = ` "Arn", ], }, - "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"SegmentResults":{"Type":"Pass","ResultPath":"$.segmentedResults","Parameters":{"discountExpiresOnDate.$":"$.discountExpiresOnDate","expiringDiscountsToProcess.$":"$.expiringDiscountsToProcess","successes.$":"States.ArrayFilter($.discountProcessingAttempts, $.emailSendAttempt.status == \\"success\\")","failures.$":"States.ArrayFilter($.discountProcessingAttempts, $.emailSendAttempt.status == \\"error\\")"},"Next":"Save results"},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"SegmentResults":{"Type":"Pass","ResultPath":"$.segmentedResults","Parameters":{"discountExpiresOnDate.$":"$.discountExpiresOnDate","expiringDiscountsToProcess.$":"$.expiringDiscountsToProcess","successes.$":"States.ArrayFilter($.discountProcessingAttempts, item.emailSendAttempt.status == 'success')","failures.$":"States.ArrayFilter($.discountProcessingAttempts, item.emailSendAttempt.status == 'error')"},"Next":"Save results"},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, @@ -1556,7 +1556,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 2`] = ` "Arn", ], }, - "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"SegmentResults":{"Type":"Pass","ResultPath":"$.segmentedResults","Parameters":{"discountExpiresOnDate.$":"$.discountExpiresOnDate","expiringDiscountsToProcess.$":"$.expiringDiscountsToProcess","successes.$":"States.ArrayFilter($.discountProcessingAttempts, $.emailSendAttempt.status == \\"success\\")","failures.$":"States.ArrayFilter($.discountProcessingAttempts, $.emailSendAttempt.status == \\"error\\")"},"Next":"Save results"},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"SegmentResults":{"Type":"Pass","ResultPath":"$.segmentedResults","Parameters":{"discountExpiresOnDate.$":"$.discountExpiresOnDate","expiringDiscountsToProcess.$":"$.expiringDiscountsToProcess","successes.$":"States.ArrayFilter($.discountProcessingAttempts, item.emailSendAttempt.status == 'success')","failures.$":"States.ArrayFilter($.discountProcessingAttempts, item.emailSendAttempt.status == 'error')"},"Next":"Save results"},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, diff --git a/cdk/lib/discount-expiry-notifier.ts b/cdk/lib/discount-expiry-notifier.ts index 91949a946e..5c1bddddf0 100644 --- a/cdk/lib/discount-expiry-notifier.ts +++ b/cdk/lib/discount-expiry-notifier.ts @@ -188,9 +188,9 @@ export class DiscountExpiryNotifier extends GuStack { 'discountExpiresOnDate.$': '$.discountExpiresOnDate', 'expiringDiscountsToProcess.$': '$.expiringDiscountsToProcess', 'successes.$': - 'States.ArrayFilter($.discountProcessingAttempts, $.emailSendAttempt.status == "success")', + "States.ArrayFilter($.discountProcessingAttempts, item.emailSendAttempt.status == 'success')", 'failures.$': - 'States.ArrayFilter($.discountProcessingAttempts, $.emailSendAttempt.status == "error")', + "States.ArrayFilter($.discountProcessingAttempts, item.emailSendAttempt.status == 'error')", }, resultPath: '$.segmentedResults', }); From ad72b0c8eafaa7246468e0b3b835e6bfac9bde3c Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 14:34:27 +0000 Subject: [PATCH 15/21] revert cdk changes --- .../discount-expiry-notifier.test.ts.snap | 8 ++++---- cdk/lib/discount-expiry-notifier.ts | 13 ------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap b/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap index 790099e14e..8f6f894371 100644 --- a/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap +++ b/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap @@ -177,7 +177,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 1`] = ` "Arn", ], }, - "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"SegmentResults","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"Next":"Is Subscription Active?","Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"Save results","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"Next":"Is Subscription Active?","Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, @@ -199,7 +199,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 1`] = ` "Arn", ], }, - "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"SegmentResults":{"Type":"Pass","ResultPath":"$.segmentedResults","Parameters":{"discountExpiresOnDate.$":"$.discountExpiresOnDate","expiringDiscountsToProcess.$":"$.expiringDiscountsToProcess","successes.$":"States.ArrayFilter($.discountProcessingAttempts, item.emailSendAttempt.status == 'success')","failures.$":"States.ArrayFilter($.discountProcessingAttempts, item.emailSendAttempt.status == 'error')"},"Next":"Save results"},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, @@ -1534,7 +1534,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 2`] = ` "Arn", ], }, - "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"SegmentResults","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"Next":"Is Subscription Active?","Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"Save results","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"Next":"Is Subscription Active?","Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, @@ -1556,7 +1556,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 2`] = ` "Arn", ], }, - "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"SegmentResults":{"Type":"Pass","ResultPath":"$.segmentedResults","Parameters":{"discountExpiresOnDate.$":"$.discountExpiresOnDate","expiringDiscountsToProcess.$":"$.expiringDiscountsToProcess","successes.$":"States.ArrayFilter($.discountProcessingAttempts, item.emailSendAttempt.status == 'success')","failures.$":"States.ArrayFilter($.discountProcessingAttempts, item.emailSendAttempt.status == 'error')"},"Next":"Save results"},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, diff --git a/cdk/lib/discount-expiry-notifier.ts b/cdk/lib/discount-expiry-notifier.ts index 5c1bddddf0..cd347b861d 100644 --- a/cdk/lib/discount-expiry-notifier.ts +++ b/cdk/lib/discount-expiry-notifier.ts @@ -183,18 +183,6 @@ export class DiscountExpiryNotifier extends GuStack { const isSubActiveChoice = new Choice(this, 'Is Subscription Active?'); - const segmentResults = new Pass(this, 'SegmentResults', { - parameters: { - 'discountExpiresOnDate.$': '$.discountExpiresOnDate', - 'expiringDiscountsToProcess.$': '$.expiringDiscountsToProcess', - 'successes.$': - "States.ArrayFilter($.discountProcessingAttempts, item.emailSendAttempt.status == 'success')", - 'failures.$': - "States.ArrayFilter($.discountProcessingAttempts, item.emailSendAttempt.status == 'error')", - }, - resultPath: '$.segmentedResults', - }); - emailSendsProcessingMap.iterator( subIsActiveLambdaTask.next( isSubActiveChoice @@ -211,7 +199,6 @@ export class DiscountExpiryNotifier extends GuStack { const definitionBody = DefinitionBody.fromChainable( getSubsWithExpiringDiscountsLambdaTask .next(emailSendsProcessingMap) - .next(segmentResults) .next(saveResultsLambdaTask), ); From 5d4eabcc8dc228349296767e8032e95db272401d Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:13:04 +0000 Subject: [PATCH 16/21] segment successes and failures and save separately to S3 --- .../src/handlers/saveResults.ts | 139 +++++++++++++++--- 1 file changed, 116 insertions(+), 23 deletions(-) diff --git a/handlers/discount-expiry-notifier/src/handlers/saveResults.ts b/handlers/discount-expiry-notifier/src/handlers/saveResults.ts index 5f11a9f29f..94523ee58d 100644 --- a/handlers/discount-expiry-notifier/src/handlers/saveResults.ts +++ b/handlers/discount-expiry-notifier/src/handlers/saveResults.ts @@ -2,23 +2,7 @@ import { getIfDefined } from '@modules/nullAndUndefined'; import { uploadFileToS3 } from '../s3'; //todo add a type check on the input event -export const handler = async (event: { - discountExpiresOnDate: string; - expiringDiscountsToProcess: Array<{ - firstName: string; - nextPaymentDate: string; - paymentAmount: number; - paymentCurrency: string; - paymentFrequency: string; - productName: string; - sfContactId: string; - subName: string; - workEmail: string; - }>; - expiringDiscountProcessingAttempts: Array<{ - status: string; - }>; -}) => { +export const handler = async (event: DiscountProcessingEvent) => { const bucketName = getIfDefined( process.env.S3_BUCKET, 'S3_BUCKET environment variable not set', @@ -28,18 +12,127 @@ export const handler = async (event: { event.discountExpiresOnDate, 'event.discountExpiresOnDate variable not set', ); - const executionDateTime = new Date().toISOString(); + const { discountProcessingAttempts } = event; - const filePath = `${discountExpiresOnDate}/${executionDateTime}`; + const successes = discountProcessingAttempts.filter( + (attempt) => attempt.emailSendAttempt.status === 'success', + ); + const successesFilePath = generateFilePath( + discountExpiresOnDate, + executionDateTime, + 'successes', + ); + const saveSuccessesAttempt = await uploadFileToS3({ + bucketName, + filePath: successesFilePath, + content: JSON.stringify(successes, null, 2), + }); - await uploadFileToS3({ + const failures = discountProcessingAttempts.filter( + (attempt) => attempt.emailSendAttempt.status === 'error', + ); + const failuresFilePath = generateFilePath( + discountExpiresOnDate, + executionDateTime, + 'failures', + ); + const saveFailuresAttempt = await uploadFileToS3({ bucketName, - filePath, - content: JSON.stringify(event), + filePath: failuresFilePath, + content: JSON.stringify(failures, null, 2), }); return { - filePath, + successesFilePath: successesFilePath, + saveSuccessesAttempt, + failuresFilePath: failuresFilePath, + saveFailuresAttempt, + }; +}; + +function generateFilePath( + discountExpiresOnDate: string, + executionDateTime: string, + status: 'successes' | 'failures', +) { + return `${discountExpiresOnDate}/${status}/${executionDateTime}`; +} + +type DiscountProcessingEvent = { + discountExpiresOnDate: string; + expiringDiscountsToProcess: ExpiringDiscount[]; + discountProcessingAttempts: DiscountProcessingAttempt[]; +}; + +type ExpiringDiscount = { + firstName: string; + nextPaymentDate: string; + paymentAmount: number; + paymentCurrency: string; + paymentFrequency: string; + productName: string; + sfContactId: string; + subName: string; + workEmail: string; +}; + +type DiscountProcessingAttempt = { + detail: DiscountDetail; + emailSendAttempt: EmailSendAttempt; +}; + +type DiscountDetail = ExpiringDiscount & { + status: string; +}; + +type EmailSendAttempt = { + status: 'success' | 'error'; + payload: EmailPayload; + response: EmailResponse; +}; + +type EmailPayload = { + To: EmailRecipient; + DataExtensionName: string; + SfContactId: string; +}; + +type EmailRecipient = { + Address: string; + ContactAttributes: { + SubscriberAttributes: SubscriberAttributes; }; }; + +type SubscriberAttributes = { + EmailAddress: string; + paymentAmount: string; + first_name: string; + date_of_payment: string; + paymentFrequency: string; +}; + +type EmailResponse = SuccessResponse | ErrorResponse; + +type SuccessResponse = { + $metadata: Metadata; + MD5OfMessageBody: string; + MessageId: string; +}; + +type ErrorResponse = { + name: string; + $fault: string; + $metadata: Metadata; + __type: string; + Code: string; + Type: string; +}; + +type Metadata = { + httpStatusCode: number; + requestId: string; + attempts: number; + totalRetryDelay: number; +}; From 490bd487b6e78ff3429fba39984ca7a340a5efb0 Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:24:17 +0000 Subject: [PATCH 17/21] remove dev testing logic --- .../src/handlers/initiateEmailSend.ts | 33 ++----------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts b/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts index bc2b983163..cdc668c586 100644 --- a/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts +++ b/handlers/discount-expiry-notifier/src/handlers/initiateEmailSend.ts @@ -1,11 +1,5 @@ -import type { SendMessageCommandOutput } from '@aws-sdk/client-sqs'; -import { SendMessageCommand, SQSClient } from '@aws-sdk/client-sqs'; -import { awsConfig } from '@modules/aws/config'; -import { DataExtensionNames } from '@modules/email/email'; -import type { EmailMessageWithUserId } from '@modules/email/email'; -import { prettyPrint } from '@modules/prettyPrint'; +import { DataExtensionNames, sendEmail } from '@modules/email/email'; import { stageFromEnvironment } from '@modules/stage'; -import type { Stage } from '@modules/stage'; export const handler = async (event: { firstName: string; @@ -40,8 +34,7 @@ export const handler = async (event: { SfContactId: event.sfContactId, }; try { - const succeed = event.subName === 'A-S00886188'; - const response = await sendEmail(stageFromEnvironment(), payload, succeed); + const response = await sendEmail(stageFromEnvironment(), payload); return { detail: event, @@ -63,28 +56,6 @@ export const handler = async (event: { } }; -export const sendEmail = async ( - stage: Stage, - emailMessage: EmailMessageWithUserId, - succeed: boolean, - log: (messsage: string) => void = console.log, -): Promise => { - const queueName = succeed - ? `braze-emails-${stage}` - : `braze-emails-${stage}-blah`; - const client = new SQSClient(awsConfig); - log( - `Sending email message ${prettyPrint(emailMessage)} to queue ${queueName}`, - ); - const command = new SendMessageCommand({ - QueueUrl: queueName, - MessageBody: JSON.stringify(emailMessage), - }); - - const response = await client.send(command); - log(`Response from email send was ${prettyPrint(response)}`); - return response; -}; function formatDate(inputDate: string): string { return new Date(inputDate).toLocaleDateString('en-GB', { day: '2-digit', From 0fb7b46b553d4987ad350f9664b379fffc8440e8 Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:43:10 +0000 Subject: [PATCH 18/21] remove check on status (for now) --- .../discount-expiry-notifier.test.ts.snap | 78 +------------------ cdk/lib/discount-expiry-notifier.ts | 29 +++---- 2 files changed, 17 insertions(+), 90 deletions(-) diff --git a/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap b/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap index 8f6f894371..b8dfa3abb2 100644 --- a/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap +++ b/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap @@ -177,7 +177,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 1`] = ` "Arn", ], }, - "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"Save results","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"Next":"Is Subscription Active?","Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"Save results","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, @@ -188,17 +188,6 @@ exports[`The discount-expiry-notifier stack matches the snapshot 1`] = ` "Arn", ], }, - "","Payload.$":"$"}},"Is Subscription Active?":{"Type":"Choice","Choices":[{"Variable":"$.status","StringEquals":"Active","Next":"Initiate email send"}],"Default":"Skip Processing"},"Skip Processing":{"Type":"Pass","ResultPath":null,"End":true},"Initiate email send":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", - { - "Ref": "AWS::Partition", - }, - ":states:::lambda:invoke","Parameters":{"FunctionName":"", - { - "Fn::GetAtt": [ - "initiateemailsendlambdaE5C79A3B", - "Arn", - ], - }, "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", @@ -368,32 +357,6 @@ exports[`The discount-expiry-notifier stack matches the snapshot 1`] = ` }, ], }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "initiateemailsendlambdaE5C79A3B", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "initiateemailsendlambdaE5C79A3B", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, ], "Version": "2012-10-17", }, @@ -1534,7 +1497,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 2`] = ` "Arn", ], }, - "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"Save results","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"Next":"Is Subscription Active?","Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"Save results","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, @@ -1545,17 +1508,6 @@ exports[`The discount-expiry-notifier stack matches the snapshot 2`] = ` "Arn", ], }, - "","Payload.$":"$"}},"Is Subscription Active?":{"Type":"Choice","Choices":[{"Variable":"$.status","StringEquals":"Active","Next":"Initiate email send"}],"Default":"Skip Processing"},"Skip Processing":{"Type":"Pass","ResultPath":null,"End":true},"Initiate email send":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", - { - "Ref": "AWS::Partition", - }, - ":states:::lambda:invoke","Parameters":{"FunctionName":"", - { - "Fn::GetAtt": [ - "initiateemailsendlambdaE5C79A3B", - "Arn", - ], - }, "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", @@ -1725,32 +1677,6 @@ exports[`The discount-expiry-notifier stack matches the snapshot 2`] = ` }, ], }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "initiateemailsendlambdaE5C79A3B", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "initiateemailsendlambdaE5C79A3B", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, ], "Version": "2012-10-17", }, diff --git a/cdk/lib/discount-expiry-notifier.ts b/cdk/lib/discount-expiry-notifier.ts index cd347b861d..b203a9ce1b 100644 --- a/cdk/lib/discount-expiry-notifier.ts +++ b/cdk/lib/discount-expiry-notifier.ts @@ -181,20 +181,21 @@ export class DiscountExpiryNotifier extends GuStack { resultPath: '$.discountProcessingAttempts', }); - const isSubActiveChoice = new Choice(this, 'Is Subscription Active?'); - - emailSendsProcessingMap.iterator( - subIsActiveLambdaTask.next( - isSubActiveChoice - .when( - Condition.stringEquals('$.status', 'Active'), - initiateEmailSendLambdaTask, - ) - .otherwise( - new Pass(this, 'Skip Processing', { resultPath: JsonPath.DISCARD }), - ), - ), - ); + // const isSubActiveChoice = new Choice(this, 'Is Subscription Active?'); + + // emailSendsProcessingMap.iterator( + // subIsActiveLambdaTask.next( + // isSubActiveChoice + // .when( + // Condition.stringEquals('$.status', 'Active'), + // initiateEmailSendLambdaTask, + // ) + // .otherwise( + // new Pass(this, 'Skip Processing', { resultPath: JsonPath.DISCARD }), + // ), + // ), + // ); + emailSendsProcessingMap.iterator(subIsActiveLambdaTask); const definitionBody = DefinitionBody.fromChainable( getSubsWithExpiringDiscountsLambdaTask From 1be1521ea7baf9b0833f15be34630a55b4ecaaaa Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:43:40 +0000 Subject: [PATCH 19/21] Update discount-expiry-notifier.ts --- cdk/lib/discount-expiry-notifier.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cdk/lib/discount-expiry-notifier.ts b/cdk/lib/discount-expiry-notifier.ts index b203a9ce1b..73926c7021 100644 --- a/cdk/lib/discount-expiry-notifier.ts +++ b/cdk/lib/discount-expiry-notifier.ts @@ -12,12 +12,12 @@ import { import { Architecture } from 'aws-cdk-lib/aws-lambda'; import { Bucket } from 'aws-cdk-lib/aws-s3'; import { - Choice, - Condition, + // Choice, + // Condition, DefinitionBody, JsonPath, Map, - Pass, + // Pass, StateMachine, } from 'aws-cdk-lib/aws-stepfunctions'; import { LambdaInvoke } from 'aws-cdk-lib/aws-stepfunctions-tasks'; From 13f6045a87c145c5cda4a5983796ee579983d63f Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:44:33 +0000 Subject: [PATCH 20/21] Update discount-expiry-notifier.ts --- cdk/lib/discount-expiry-notifier.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cdk/lib/discount-expiry-notifier.ts b/cdk/lib/discount-expiry-notifier.ts index 73926c7021..2deea80a52 100644 --- a/cdk/lib/discount-expiry-notifier.ts +++ b/cdk/lib/discount-expiry-notifier.ts @@ -163,14 +163,14 @@ export class DiscountExpiryNotifier extends GuStack { outputPath: '$.Payload', }); - const initiateEmailSendLambdaTask = new LambdaInvoke( - this, - 'Initiate email send', - { - lambdaFunction: initiateEmailSendLambda, - outputPath: '$.Payload', - }, - ); + // const initiateEmailSendLambdaTask = new LambdaInvoke( + // this, + // 'Initiate email send', + // { + // lambdaFunction: initiateEmailSendLambda, + // outputPath: '$.Payload', + // }, + // ); const emailSendsProcessingMap = new Map(this, 'Email sends processor map', { maxConcurrency: 10, From 3402f55de1a34dc28887afc8bcb05bba3f9375a4 Mon Sep 17 00:00:00 2001 From: David Pepper <36296660+david-pepper@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:58:14 +0000 Subject: [PATCH 21/21] revert changes --- .../discount-expiry-notifier.test.ts.snap | 78 ++++++++++++++++++- cdk/lib/discount-expiry-notifier.ts | 51 ++++++------ 2 files changed, 101 insertions(+), 28 deletions(-) diff --git a/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap b/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap index b8dfa3abb2..8f6f894371 100644 --- a/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap +++ b/cdk/lib/__snapshots__/discount-expiry-notifier.test.ts.snap @@ -177,7 +177,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 1`] = ` "Arn", ], }, - "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"Save results","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"Save results","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"Next":"Is Subscription Active?","Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, @@ -188,6 +188,17 @@ exports[`The discount-expiry-notifier stack matches the snapshot 1`] = ` "Arn", ], }, + "","Payload.$":"$"}},"Is Subscription Active?":{"Type":"Choice","Choices":[{"Variable":"$.status","StringEquals":"Active","Next":"Initiate email send"}],"Default":"Skip Processing"},"Skip Processing":{"Type":"Pass","ResultPath":null,"End":true},"Initiate email send":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + { + "Ref": "AWS::Partition", + }, + ":states:::lambda:invoke","Parameters":{"FunctionName":"", + { + "Fn::GetAtt": [ + "initiateemailsendlambdaE5C79A3B", + "Arn", + ], + }, "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", @@ -357,6 +368,32 @@ exports[`The discount-expiry-notifier stack matches the snapshot 1`] = ` }, ], }, + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "initiateemailsendlambdaE5C79A3B", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "initiateemailsendlambdaE5C79A3B", + "Arn", + ], + }, + ":*", + ], + ], + }, + ], + }, ], "Version": "2012-10-17", }, @@ -1497,7 +1534,7 @@ exports[`The discount-expiry-notifier stack matches the snapshot 2`] = ` "Arn", ], }, - "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"Save results","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + "","Payload.$":"$"}},"Email sends processor map":{"Type":"Map","ResultPath":"$.discountProcessingAttempts","Next":"Save results","Parameters":{"item.$":"$$.Map.Item.Value"},"Iterator":{"StartAt":"Check sub is active","States":{"Check sub is active":{"Next":"Is Subscription Active?","Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", }, @@ -1508,6 +1545,17 @@ exports[`The discount-expiry-notifier stack matches the snapshot 2`] = ` "Arn", ], }, + "","Payload.$":"$"}},"Is Subscription Active?":{"Type":"Choice","Choices":[{"Variable":"$.status","StringEquals":"Active","Next":"Initiate email send"}],"Default":"Skip Processing"},"Skip Processing":{"Type":"Pass","ResultPath":null,"End":true},"Initiate email send":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", + { + "Ref": "AWS::Partition", + }, + ":states:::lambda:invoke","Parameters":{"FunctionName":"", + { + "Fn::GetAtt": [ + "initiateemailsendlambdaE5C79A3B", + "Arn", + ], + }, "","Payload.$":"$"}}}},"ItemsPath":"$.expiringDiscountsToProcess","MaxConcurrency":10},"Save results":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","OutputPath":"$.Payload","Resource":"arn:", { "Ref": "AWS::Partition", @@ -1677,6 +1725,32 @@ exports[`The discount-expiry-notifier stack matches the snapshot 2`] = ` }, ], }, + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "initiateemailsendlambdaE5C79A3B", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "initiateemailsendlambdaE5C79A3B", + "Arn", + ], + }, + ":*", + ], + ], + }, + ], + }, ], "Version": "2012-10-17", }, diff --git a/cdk/lib/discount-expiry-notifier.ts b/cdk/lib/discount-expiry-notifier.ts index 2deea80a52..cd347b861d 100644 --- a/cdk/lib/discount-expiry-notifier.ts +++ b/cdk/lib/discount-expiry-notifier.ts @@ -12,12 +12,12 @@ import { import { Architecture } from 'aws-cdk-lib/aws-lambda'; import { Bucket } from 'aws-cdk-lib/aws-s3'; import { - // Choice, - // Condition, + Choice, + Condition, DefinitionBody, JsonPath, Map, - // Pass, + Pass, StateMachine, } from 'aws-cdk-lib/aws-stepfunctions'; import { LambdaInvoke } from 'aws-cdk-lib/aws-stepfunctions-tasks'; @@ -163,14 +163,14 @@ export class DiscountExpiryNotifier extends GuStack { outputPath: '$.Payload', }); - // const initiateEmailSendLambdaTask = new LambdaInvoke( - // this, - // 'Initiate email send', - // { - // lambdaFunction: initiateEmailSendLambda, - // outputPath: '$.Payload', - // }, - // ); + const initiateEmailSendLambdaTask = new LambdaInvoke( + this, + 'Initiate email send', + { + lambdaFunction: initiateEmailSendLambda, + outputPath: '$.Payload', + }, + ); const emailSendsProcessingMap = new Map(this, 'Email sends processor map', { maxConcurrency: 10, @@ -181,21 +181,20 @@ export class DiscountExpiryNotifier extends GuStack { resultPath: '$.discountProcessingAttempts', }); - // const isSubActiveChoice = new Choice(this, 'Is Subscription Active?'); - - // emailSendsProcessingMap.iterator( - // subIsActiveLambdaTask.next( - // isSubActiveChoice - // .when( - // Condition.stringEquals('$.status', 'Active'), - // initiateEmailSendLambdaTask, - // ) - // .otherwise( - // new Pass(this, 'Skip Processing', { resultPath: JsonPath.DISCARD }), - // ), - // ), - // ); - emailSendsProcessingMap.iterator(subIsActiveLambdaTask); + const isSubActiveChoice = new Choice(this, 'Is Subscription Active?'); + + emailSendsProcessingMap.iterator( + subIsActiveLambdaTask.next( + isSubActiveChoice + .when( + Condition.stringEquals('$.status', 'Active'), + initiateEmailSendLambdaTask, + ) + .otherwise( + new Pass(this, 'Skip Processing', { resultPath: JsonPath.DISCARD }), + ), + ), + ); const definitionBody = DefinitionBody.fromChainable( getSubsWithExpiringDiscountsLambdaTask