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
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
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)
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.