diff --git a/eventbridge-schedule-dynamic-with-auto-deletion/README.md b/eventbridge-schedule-dynamic-with-auto-deletion/README.md
new file mode 100644
index 000000000..bc97bb9fb
--- /dev/null
+++ b/eventbridge-schedule-dynamic-with-auto-deletion/README.md
@@ -0,0 +1,73 @@
+# Dynamic schedules with auto deletion with Amazon EventBridge Scheduler
+Event-driven pattern that creates three schedules when records get added to a DynamoDB table. These schedules trigger an email service and are automatically deleted.
+
+![alt](./architecture.png)
+
+Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/eventbridge-schedule-dynamic-with-auto-deletion.
+
+Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.
+
+
+## Requirements
+
+- [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
+- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
+- [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
+- [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed and configured
+
+## Deployment Instructions
+
+1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
+ ```bash
+ git clone https://github.com/aws-samples/serverless-patterns
+ ```
+2. Change directory to the pattern directory:
+ ```bash
+ cd serverless-patterns/eventbridge-schedule-dynamic-with-auto-deletion/src
+ ```
+3. Install dependencies:
+ ```bash
+ npm install
+ ```
+4. From the command line, configure AWS CDK:
+ ```bash
+ cdk bootstrap ACCOUNT-NUMBER/REGION # e.g.
+ cdk bootstrap 1111111111/us-east-1
+ cdk bootstrap --profile test 1111111111/us-east-1
+ ```
+5. From the command line, use AWS CDK to deploy the AWS resources for the pattern as specified in the `lib/src-stack.ts` file:
+ ```bash
+ npm run build && cdk deploy
+ ```
+
+## How it works
+
+- New record is inserted into DynamoDB (new user signs up)
+- EventBridge Pipes is used to connect the DynamoDB stream to EventBridge.
+- Every time an `INSERT` happens, a new EventBridge event is raised `NewUserCreated`
+- ScheduleCreator Lambda function is then triggered, creating three schedules for the user using Amazon EventBridge Scheduler.
+- All schedules are removed once they run (auto deletion).
+- Each schedule triggers the `Email Service` Lambda function. (Placeholder of what an email service could be.)
+
+## Testing
+
+Deploy the stack and run the script:
+
+```sh
+sh src/insert-record.sh
+```
+
+This script will insert a new record into your new database `ServerlessLandUsers`. Then open the console and go to EventBridge Schedules. You will see three schedules created for each user (2 minutes from creation, 24 hours and 1 week).
+
+## Cleanup
+
+1. Delete the stack
+ ```bash
+ cdk destroy
+ ```
+
+---
+
+Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+SPDX-License-Identifier: MIT-0
\ No newline at end of file
diff --git a/eventbridge-schedule-dynamic-with-auto-deletion/architecture.png b/eventbridge-schedule-dynamic-with-auto-deletion/architecture.png
new file mode 100644
index 000000000..4300a8133
Binary files /dev/null and b/eventbridge-schedule-dynamic-with-auto-deletion/architecture.png differ
diff --git a/eventbridge-schedule-dynamic-with-auto-deletion/example-pattern.json b/eventbridge-schedule-dynamic-with-auto-deletion/example-pattern.json
new file mode 100644
index 000000000..125d9c1f1
--- /dev/null
+++ b/eventbridge-schedule-dynamic-with-auto-deletion/example-pattern.json
@@ -0,0 +1,55 @@
+{
+ "title": "Dynamic schedules with auto deletion with Amazon EventBridge Scheduler",
+ "description": "Event-driven pattern that creates three schedules when records get added to a DynamoDB table. These schedules trigger an email service and are automatically deleted.",
+ "language": "TypeScript",
+ "level": "200",
+ "framework": "CDK",
+ "introBox": {
+ "headline": "How it works",
+ "text": [
+ "When a new user signs up a record is added into the DynamoDB table. Using EventBridge pipes, changes are captured and transformed into an EventBridge event. Downstream consumers listen and create schedules for each user that automatically delete."
+ ]
+ },
+ "gitHub": {
+ "template": {
+ "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/eventbridge-schedule-dynamic-with-auto-deletion",
+ "templateURL": "serverless-patterns/eventbridge-schedule-dynamic-with-auto-deletion",
+ "projectFolder": "eventbridge-schedule-dynamic-with-auto-deletion",
+ "templateFile": "src/src/schedule-creator/index.ts"
+ }
+ },
+ "resources": {
+ "bullets": [
+ {
+ "text": "Automatically delete schedules upon completion with Amazon EventBridge Scheduler",
+ "link": "https://aws.amazon.com/blogs/compute/automatically-delete-schedules-upon-completion-with-amazon-eventbridge-scheduler/"
+ },
+ {
+ "text": "EventBridge Visuals",
+ "link": "https://serverlessland.com/serverless/visuals/eventbridge"
+ }
+ ]
+ },
+ "deploy": {
+ "text": [
+ "cdk deploy
"
+ ]
+ },
+ "testing": {
+ "text": [
+ "See the GitHub repo for detailed testing instructions."
+ ]
+ },
+ "cleanup": {
+ "text": [
+ "Delete the stack: cdk destroy
."
+ ]
+ },
+ "authors": [
+ {
+ "name": "David Boyne",
+ "image": "../assets/images/resources/dboyne.png",
+ "bio": "Senior Developer Advocate at AWS focusing on EDA and Serverless."
+ }
+ ]
+}
diff --git a/eventbridge-schedule-dynamic-with-auto-deletion/src/.gitignore b/eventbridge-schedule-dynamic-with-auto-deletion/src/.gitignore
new file mode 100644
index 000000000..f60797b6a
--- /dev/null
+++ b/eventbridge-schedule-dynamic-with-auto-deletion/src/.gitignore
@@ -0,0 +1,8 @@
+*.js
+!jest.config.js
+*.d.ts
+node_modules
+
+# CDK asset staging directory
+.cdk.staging
+cdk.out
diff --git a/eventbridge-schedule-dynamic-with-auto-deletion/src/.npmignore b/eventbridge-schedule-dynamic-with-auto-deletion/src/.npmignore
new file mode 100644
index 000000000..c1d6d45dc
--- /dev/null
+++ b/eventbridge-schedule-dynamic-with-auto-deletion/src/.npmignore
@@ -0,0 +1,6 @@
+*.ts
+!*.d.ts
+
+# CDK asset staging directory
+.cdk.staging
+cdk.out
diff --git a/eventbridge-schedule-dynamic-with-auto-deletion/src/README.md b/eventbridge-schedule-dynamic-with-auto-deletion/src/README.md
new file mode 100644
index 000000000..1b808b2cc
--- /dev/null
+++ b/eventbridge-schedule-dynamic-with-auto-deletion/src/README.md
@@ -0,0 +1,15 @@
+# Welcome to your CDK TypeScript project
+
+You should explore the contents of this project. It demonstrates a CDK app with an instance of a stack (`SrcStack`)
+which contains an Amazon SQS queue that is subscribed to an Amazon SNS topic.
+
+The `cdk.json` file tells the CDK Toolkit how to execute your app.
+
+## Useful commands
+
+* `npm run build` compile typescript to js
+* `npm run watch` watch for changes and compile
+* `npm run test` perform the jest unit tests
+* `cdk deploy` deploy this stack to your default AWS account/region
+* `cdk diff` compare deployed stack with current state
+* `cdk synth` emits the synthesized CloudFormation template
diff --git a/eventbridge-schedule-dynamic-with-auto-deletion/src/bin/src.ts b/eventbridge-schedule-dynamic-with-auto-deletion/src/bin/src.ts
new file mode 100644
index 000000000..a54ff9026
--- /dev/null
+++ b/eventbridge-schedule-dynamic-with-auto-deletion/src/bin/src.ts
@@ -0,0 +1,6 @@
+#!/usr/bin/env node
+import * as cdk from 'aws-cdk-lib';
+import { DynamicEventBridgeSchedulesStack } from '../lib/src-stack';
+
+const app = new cdk.App();
+new DynamicEventBridgeSchedulesStack(app, 'DynamicEventBridgeSchedules');
diff --git a/eventbridge-schedule-dynamic-with-auto-deletion/src/cdk.json b/eventbridge-schedule-dynamic-with-auto-deletion/src/cdk.json
new file mode 100644
index 000000000..36b0ca2ee
--- /dev/null
+++ b/eventbridge-schedule-dynamic-with-auto-deletion/src/cdk.json
@@ -0,0 +1,44 @@
+{
+ "app": "npx ts-node --prefer-ts-exts bin/src.ts",
+ "watch": {
+ "include": [
+ "**"
+ ],
+ "exclude": [
+ "README.md",
+ "cdk*.json",
+ "**/*.d.ts",
+ "**/*.js",
+ "tsconfig.json",
+ "package*.json",
+ "yarn.lock",
+ "node_modules",
+ "test"
+ ]
+ },
+ "context": {
+ "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
+ "@aws-cdk/core:checkSecretUsage": true,
+ "@aws-cdk/core:target-partitions": [
+ "aws",
+ "aws-cn"
+ ],
+ "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
+ "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
+ "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
+ "@aws-cdk/aws-iam:minimizePolicies": true,
+ "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
+ "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
+ "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
+ "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
+ "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
+ "@aws-cdk/core:enablePartitionLiterals": true,
+ "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
+ "@aws-cdk/aws-iam:standardizedServicePrincipals": true,
+ "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
+ "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
+ "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
+ "@aws-cdk/aws-route53-patters:useCertificate": true,
+ "@aws-cdk/customresources:installLatestAwsSdkDefault": false
+ }
+}
diff --git a/eventbridge-schedule-dynamic-with-auto-deletion/src/jest.config.js b/eventbridge-schedule-dynamic-with-auto-deletion/src/jest.config.js
new file mode 100644
index 000000000..08263b895
--- /dev/null
+++ b/eventbridge-schedule-dynamic-with-auto-deletion/src/jest.config.js
@@ -0,0 +1,8 @@
+module.exports = {
+ testEnvironment: 'node',
+ roots: ['/test'],
+ testMatch: ['**/*.test.ts'],
+ transform: {
+ '^.+\\.tsx?$': 'ts-jest'
+ }
+};
diff --git a/eventbridge-schedule-dynamic-with-auto-deletion/src/lib/src-stack.ts b/eventbridge-schedule-dynamic-with-auto-deletion/src/lib/src-stack.ts
new file mode 100644
index 000000000..f6543206e
--- /dev/null
+++ b/eventbridge-schedule-dynamic-with-auto-deletion/src/lib/src-stack.ts
@@ -0,0 +1,147 @@
+import * as cdk from 'aws-cdk-lib';
+import { Construct } from 'constructs';
+import * as events from 'aws-cdk-lib/aws-events';
+import * as targets from 'aws-cdk-lib/aws-events-targets';
+import * as logs from 'aws-cdk-lib/aws-logs';
+import * as iam from 'aws-cdk-lib/aws-iam';
+import * as pipes from 'aws-cdk-lib/aws-pipes';
+import * as lambda from 'aws-cdk-lib/aws-lambda';
+import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
+import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
+import * as path from 'path';
+import { CfnScheduleGroup } from 'aws-cdk-lib/aws-scheduler';
+import { LambdaTarget } from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets';
+
+export class DynamicEventBridgeSchedulesStack extends cdk.Stack {
+ constructor(scope: Construct, id: string, props?: cdk.StackProps) {
+ super(scope, id, props);
+
+ // The DynamoDB table for users
+ const usersTable = new dynamodb.Table(this, 'usersTable', {
+ tableName: 'ServerlessLandUsers',
+ partitionKey: {
+ name: 'userId',
+ type: dynamodb.AttributeType.STRING,
+ },
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
+ stream: dynamodb.StreamViewType.NEW_IMAGE,
+ });
+
+ // Event Bus used for application
+ const eventBus = new events.EventBus(this, 'ServerlessLandEventBus', {
+ eventBusName: 'ServerlessLandEventBus',
+ });
+
+ // Schedule group for all the user schedules
+ const userScheduleGroup = new CfnScheduleGroup(this, 'UserScheduleGroup', {
+ name: 'UserScheduleGroup',
+ });
+
+ // Example function that would be the email service.
+ const emailService: NodejsFunction = new NodejsFunction(this, 'email-service', {
+ memorySize: 1024,
+ timeout: cdk.Duration.seconds(5),
+ runtime: lambda.Runtime.NODEJS_18_X,
+ handler: 'handler',
+ entry: path.join(__dirname, '../src/email-service/index.ts'),
+ });
+
+ const scheduleRole = new iam.Role(this, 'scheduleRole', {
+ assumedBy: new iam.ServicePrincipal('scheduler.amazonaws.com'),
+ });
+
+ emailService.grantInvoke(scheduleRole);
+
+ // Example function that will create schedules for users
+ const scheduleCreator: NodejsFunction = new NodejsFunction(this, 'schedule-creator', {
+ memorySize: 1024,
+ timeout: cdk.Duration.seconds(5),
+ runtime: lambda.Runtime.NODEJS_18_X,
+ handler: 'handler',
+ entry: "./src/schedule-creator/index.ts",
+ bundling: {
+ nodeModules: ['@aws-sdk/client-scheduler']
+ },
+ environment: {
+ SCHEDULE_GROUP_NAME: userScheduleGroup.name || '',
+ EMAIL_SERVICE_ARN: emailService.functionArn,
+ SCHEDULE_ROLE_ARN: scheduleRole.roleArn
+ },
+ initialPolicy: [
+ // Give lambda permission to create group, schedule and pass IAM role to the scheduler
+ new iam.PolicyStatement({
+ // actions: ['scheduler:CreateSchedule', 'iam:PassRole', 'scheduler:CreateScheduleGroup'],
+ actions: ['scheduler:CreateSchedule', 'iam:PassRole'],
+ resources: ['*'],
+ }),
+ ],
+ });
+
+ // CloudWatch Log group to catch all events through this event bus, for debugging.
+ new events.Rule(this, 'catchAllLogRule', {
+ ruleName: 'catch-all',
+ eventBus: eventBus,
+ eventPattern: {
+ source: events.Match.prefix(''),
+ },
+ targets: [
+ new targets.CloudWatchLogGroup(
+ new logs.LogGroup(this, 'ServerlessLandEventBusAllEvents', {
+ logGroupName: '/aws/events/ServerlessLandEventBus/logs',
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
+ })
+ ),
+ ],
+ });
+
+ // Create a rule. Listen for event and trigger lambda to create schedule
+ new events.Rule(this, 'create-schedules', {
+ ruleName: 'create-schedules-on-new-user',
+ eventBus: eventBus,
+ eventPattern: {
+ source: events.Match.exactString('serverlessland.users'),
+ detailType: events.Match.exactString('NewUserCreated'),
+ },
+ targets: [
+ new targets.LambdaFunction(scheduleCreator)
+ ]
+ });
+
+ const pipeRole = new iam.Role(this, 'pipeRole', {
+ assumedBy: new iam.ServicePrincipal('pipes.amazonaws.com'),
+ });
+
+
+
+ usersTable.grantReadWriteData(scheduleCreator);
+ usersTable.grantStreamRead(pipeRole);
+ eventBus.grantPutEventsTo(pipeRole);
+
+ // Create EventBridge Pipe, to connect new DynamoDB items to EventBridge.
+ new pipes.CfnPipe(this, 'pipe', {
+ roleArn: pipeRole.roleArn,
+ source: usersTable.tableStreamArn!,
+ sourceParameters: {
+ dynamoDbStreamParameters: {
+ startingPosition: 'LATEST',
+ batchSize: 3,
+ },
+ filterCriteria: {
+ filters: [
+ {
+ pattern: '{"eventName" : ["INSERT"] }',
+ },
+ ],
+ },
+ },
+ target: eventBus.eventBusArn,
+ targetParameters: {
+ eventBridgeEventBusParameters: {
+ detailType: 'NewUserCreated',
+ source: 'serverlessland.users',
+ },
+ inputTemplate: '{"userId": <$.dynamodb.NewImage.userId.S>}',
+ },
+ });
+ }
+}
diff --git a/eventbridge-schedule-dynamic-with-auto-deletion/src/package.json b/eventbridge-schedule-dynamic-with-auto-deletion/src/package.json
new file mode 100644
index 000000000..2b3202682
--- /dev/null
+++ b/eventbridge-schedule-dynamic-with-auto-deletion/src/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "eventbridge-schedule-auto-delete",
+ "version": "0.1.0",
+ "bin": {
+ "src": "bin/src.js"
+ },
+ "scripts": {
+ "build": "tsc",
+ "watch": "tsc -w",
+ "test": "jest",
+ "cdk": "cdk"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.4.0",
+ "@types/node": "18.11.18",
+ "aws-cdk": "2.64.0",
+ "jest": "^29.4.1",
+ "ts-jest": "^29.0.5",
+ "ts-node": "^10.9.1",
+ "typescript": "~4.9.5"
+ },
+ "dependencies": {
+ "@aws-sdk/client-scheduler": "^3.387.0",
+ "aws-cdk-lib": "2.64.0",
+ "constructs": "^10.0.0",
+ "date-fns": "^2.30.0"
+ }
+}
diff --git a/eventbridge-schedule-dynamic-with-auto-deletion/src/scripts/insert-record.sh b/eventbridge-schedule-dynamic-with-auto-deletion/src/scripts/insert-record.sh
new file mode 100644
index 000000000..66c01c597
--- /dev/null
+++ b/eventbridge-schedule-dynamic-with-auto-deletion/src/scripts/insert-record.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Generate random ID
+USER_ID=$(LC_ALL=C tr -dc 'a-zA-Z0-9' {
+ return client.send(
+ new CreateScheduleCommand({
+ Name: name,
+ GroupName: process.env.SCHEDULE_GROUP_NAME,
+ Target: {
+ RoleArn: process.env.SCHEDULE_ROLE_ARN,
+ Arn: process.env.EMAIL_SERVICE_ARN,
+ Input: JSON.stringify({ ...payload }),
+ },
+ ActionAfterCompletion: ActionAfterCompletion.DELETE,
+ FlexibleTimeWindow: {
+ Mode: FlexibleTimeWindowMode.OFF,
+ },
+ Description: description,
+ ScheduleExpression: time,
+ })
+ );
+};
+
+// Function that listens for new users, and schedules emails for them
+export async function handler(event: any) {
+ const userId = event.detail.userId;
+
+ // Schedule for welcome email 2 minutes after sign up
+ await createSchedule({
+ name: `${userId}-24hr-after-signup`,
+ description: `Welcome email for user:${userId}`,
+ payload: { ...event.detail, context: '24hr' },
+ time: `at(${addMinutes(new Date(), 2).toISOString().split('.')[0]})`,
+ });
+
+ // Schedule for 1 week after sign up
+ await createSchedule({
+ name: `${userId}-1wk-after-signup`,
+ description: `Email 1 week after signup for user:${userId}`,
+ payload: { ...event.detail, context: '1wk' },
+ time: `at(${addDays(new Date(), 1).toISOString().split('.')[0]})`,
+ });
+
+ // Schedule for 1 month after sign up
+ await createSchedule({
+ name: `${userId}-1month-after-signup`,
+ description: `Email 1 month after signup for user:${userId}`,
+ payload: { ...event.detail, context: '1month' },
+ time: `at(${addMonths(new Date(), 1).toISOString().split('.')[0]})`,
+ });
+}
diff --git a/eventbridge-schedule-dynamic-with-auto-deletion/src/tsconfig.json b/eventbridge-schedule-dynamic-with-auto-deletion/src/tsconfig.json
new file mode 100644
index 000000000..fc44377a1
--- /dev/null
+++ b/eventbridge-schedule-dynamic-with-auto-deletion/src/tsconfig.json
@@ -0,0 +1,30 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "commonjs",
+ "lib": [
+ "es2020"
+ ],
+ "declaration": true,
+ "strict": true,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "noImplicitThis": true,
+ "alwaysStrict": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": false,
+ "inlineSourceMap": true,
+ "inlineSources": true,
+ "experimentalDecorators": true,
+ "strictPropertyInitialization": false,
+ "typeRoots": [
+ "./node_modules/@types"
+ ]
+ },
+ "exclude": [
+ "node_modules",
+ "cdk.out"
+ ]
+}