Schedule lambda function execution using AWS SDK

0

I want to create schedule programatically to trigger lambda function at newScheduledTimeISO

Here is code that I use (it doesn't work as I want)

"use server"

import { CreateScheduleCommand } from "@aws-sdk/client-scheduler"
import { AddPermissionCommand, RemovePermissionCommand, InvokeCommand } from "@aws-sdk/client-lambda" // Ensure this import is present
import { revalidatePath } from "next/cache"
import moment from "moment-timezone"
import lambdaClient from "@/libs/aws/lambdaClient"
import { schedulerClient } from "@/libs/aws/schedulerClient"
import { PutRuleCommand, PutTargetsCommand } from "@aws-sdk/client-eventbridge"
import eventBridgeClient from "@/libs/aws/eventBridgeClient"

export async function scheduleEmailCloudWatchAction(
  newScheduledOutreachAtISO: string,
  id: number,
  emailFrom: string,
  emailTo: string,
  subject: string,
  body: string,
) {
  // Sanitize the schedule name by replacing non-alphanumeric characters with underscores
  const sanitizedTimestamp = newScheduledOutreachAtISO.replace(/[^A-Za-z0-9]/g, "_")
  const scheduleName = `ScheduleEmail_${sanitizedTimestamp}`

  // Prepare the input payload for the Lambda function
  const lambdaPayload = JSON.stringify({
    newScheduledOutreachAtISO,
    id,
    emailFrom,
    emailTo,
    subject,
    body,
  })

  // FOR TEST TRIGGER LAMBDA FUNCTION WITH PAYLOAD NOW
  try {
    const invokeCommand = new InvokeCommand({
      FunctionName: process.env.NEXT_PUBLIC_LAMBDA_FN_NAME,
      InvocationType: "Event", // Use "Event" for asynchronous invocation
      Payload: Buffer.from(lambdaPayload),
    })
    const invokeResponse = await lambdaClient.send(invokeCommand)
    console.log("Lambda function triggered successfully:", invokeResponse)
  } catch (err: any) {
    console.error("Error triggering Lambda function:", err)
  }

  // Create a schedule to trigger the Lambda function at the specific time
  const scheduleCommand = new CreateScheduleCommand({
    Name: scheduleName,
    ScheduleExpression: `at(${moment(newScheduledOutreachAtISO).utc().format("YYYY-MM-DDTHH:mm:ss")})`, // Use ISO 8601 format
    Target: {
      Arn: `arn:aws:lambda:${process.env.NEXT_PUBLIC_AWS_REGION}:${process.env.AWS_ACCOUNT_ID}:function:${process.env.NEXT_PUBLIC_LAMBDA_FN_NAME}`,
      RoleArn: (deleted due to security reasons),
      Input: lambdaPayload,
    },
    State: "ENABLED",
    FlexibleTimeWindow: {
      Mode: "OFF",
    },
  })

  try {
    // Create the schedule
    const responseSchedule = await schedulerClient.send(scheduleCommand)
    console.log(65, "responseSchedule - ", responseSchedule)
    const ruleName = `ScheduleEmail_${sanitizedTimestamp}`

    // Create EventBridge rule to trigger at the specified time
    const ruleCommand = new PutRuleCommand({
      Name: ruleName,
      ScheduleExpression: `cron(${moment(newScheduledOutreachAtISO).utc().format("m H D M ? YYYY")})`, // Convert to cron expression
      State: "ENABLED",
    })
    const ruleResponse = await eventBridgeClient.send(ruleCommand)
    console.log("EventBridge Rule ARN:", ruleResponse.RuleArn)

    // Generate a unique StatementId (alphanumeric and underscores only)
    const uniqueStatementId = `AllowInvokeFromSchedule_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`

    // Add permission to the Lambda function to be invoked by the EventBridge rule
    const lambdaArn = `arn:aws:lambda:${process.env.NEXT_PUBLIC_AWS_REGION}:${process.env.AWS_ACCOUNT_ID}:function:${process.env.NEXT_PUBLIC_LAMBDA_FN_NAME}`

    try {
      await lambdaClient.send(
        new AddPermissionCommand({
          Action: "lambda:InvokeFunction",
          FunctionName: process.env.NEXT_PUBLIC_LAMBDA_FN_NAME,
          Principal: "events.amazonaws.com",
          StatementId: uniqueStatementId,
          SourceArn: ruleResponse.RuleArn,
        }),
      )
      console.log("Permission added successfully")
    } catch (err: any) {
      if (err.name === "ResourceConflictException") {
        // Handle the conflict: remove existing permission and retry
        await lambdaClient.send(
          new RemovePermissionCommand({
            FunctionName: process.env.NEXT_PUBLIC_LAMBDA_FN_NAME,
            StatementId: uniqueStatementId,
          }),
        )
        await lambdaClient.send(
          new AddPermissionCommand({
            Action: "lambda:InvokeFunction",
            FunctionName: process.env.NEXT_PUBLIC_LAMBDA_FN_NAME,
            Principal: "events.amazonaws.com",
            StatementId: uniqueStatementId,
            SourceArn: ruleResponse.RuleArn,
          }),
        )
        console.log("Permission updated successfully after conflict resolution")
      } else {
        console.error("Error adding permission:", err)
      }
    }

    // Set the EventBridge rule target without RoleArn
    await eventBridgeClient.send(
      new PutTargetsCommand({
        Rule: ruleName,
        Targets: [
          {
            Arn: lambdaArn,
            Id: `target-${ruleName}`,
          },
        ],
      }),
    )
    console.log("Lambda function added as target to EventBridge rule.")

    // Proceed to revalidate the path
    revalidatePath("/")
  } catch (error) {
    console.error("Error creating schedule or adding permission:", error)
  }
}

I'm expecting minimal code from you that works or codesandbox

Note that I'm not expecting that you just send me docs link

I tried ask chatGPT-4o how to do that but it doesn't work

I see 201 status in console log

Also I see rules that I created aws events list-rules { "Rules": [ { "Name": "ScheduleEmail_2024_08_14T12_53_19_098Z", "Arn": "arn:aws:events:eu-north-1:975050352152:rule/ScheduleEmail_2024_08_14T12_53_19_098Z", "State": "ENABLED", "ScheduleExpression": "cron(53 12 14 8 ? 2024)", "EventBusName": "default" }, // etc

I use Next + TypeScript + Tailwind

2 Answers
2
Accepted Answer

Not sure what exactly you are trying to do. If all you want is schedule a Lambda invocation at a specific time, just use EventBridge Scheduler. In your code above it seems that you are using both EventBridge Scheduler and EventBridge Rules.

The following code should work:

import { SchedulerClient, CreateScheduleCommand } from "@aws-sdk/client-scheduler";

// Configure AWS SDK
const client = new SchedulerClient({ region: "us-east-1" }); // Change to your region

// Define the Lambda function ARN and the specific time for the schedule
const lambdaFunctionArn = "arn:aws:lambda:us-east-1:123456789012:function:MyScheduledFunction"; // Replace with your Lambda ARN
const scheduleTime = "2024-08-21T14:00:00Z"; // ISO 8601 format for UTC time

async function createOneTimeSchedule() {
    // Define the schedule
    const params = {
        Name: "MyOneTimeLambdaInvocation",
        ScheduleExpression: `at(${scheduleTime})`,
        Target: {
            Arn: lambdaFunctionArn,
            RoleArn: "arn:aws:iam::123456789012:role/MySchedulerRole", // Replace with your IAM role ARN
            Input: JSON.stringify({ key: "value" }) // Optional input to the Lambda function
        },
        FlexibleTimeWindow: {
            Mode: "OFF" // Optional: Configure flexible time windows
        },
        State: "ENABLED"
    };

    try {
        const command = new CreateScheduleCommand(params);
        const response = await client.send(command);
        console.log("One-time schedule created successfully:", response);
    } catch (error) {
        console.error("Error creating one-time schedule:", error);
    }
}

createOneTimeSchedule();
profile pictureAWS
EXPERT
Uri
answered 2 months ago
profile picture
EXPERT
reviewed 2 months ago
profile picture
EXPERT
reviewed 2 months ago
  • It doesn't work for me

    Here is the video that confirms that - https://streamable.com/qws9la

    On video you can see that in vs code terminal in console log it says that it scheduled in 1 minute from now but it don't send email

    I'm expecting that you help me thoubleshot it somehow If you need you may request more information that might help you

    I'm sure taht code for my lambda function set up correctly and work with manual scheduling

  • EventBridge Scheduler uses local region times. You should not convert to UTC. In the video it seems you are trying to schedule something to 11:07, while the local time is 13:06. This will not work (unless you are in a different timezone compared to the region you are running in).

    BTW, the fact that you do not get a mail does not mean that the scheduler did not work. It could be that the function you invoked failed to send the mail for different reasons.

  • Uri I not agree with you because

    1. About timezones - I use moment-timezone library that pass ISO format into the ScheduleExpression - its like timestampz - in this time also exist timezone - that's why it doesn't matter difference between my local time and time I schedule (because as you see I'm in GMT+2 and schedule in GMT - and it still not work)

    2. About lambda function execution failure Screenshot 1 - https://i.imgur.com/sbVjo8l.png Screenshot 2 - https://i.imgur.com/lfSgOsD.png As you see from screenshots work only recurring schedule (every hour) that I have setup manually It means that code for lambda function written correctly and its something to do with scheduling

    Anyway thank you for your answer (I thought that my question will be lost among of tons of other questions)

  • Sorry, my bad about the scheduled time. The time that you specify in ScheduleExpression is UTC, unless you also specify a timezone in ScheduleExpressionTimezone. So make sure that you either use the right time in UTC or specify a timezone. I am not familiar with moment, so maybe it just translates local times to UTC time, and that should be OK.

    I suggest that you look at the EventBridge Scheduler console and check if the schedule was created, and for what time. Maybe try creating it for a few more minutes in the future and not only 1 min.

0

You can help me here now - https://meet.google.com/yiy-pbnd-ygo?pli=1

I mention your solution here

So you help me and others to create schedule taht work

answered 2 months ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions