Skip to main content

Configuration

Define cron schedules directly in your function’s config. No external configuration needed.
index.ts
import { define, z } from "@jelou/functions";

export default define({
  name: "appointment-reminder",
  description: "Sends appointment reminders via WhatsApp",
  input: z.object({}),
  config: {
    cron: [
      { expression: "0 8 * * *", timezone: "America/Guayaquil" },
      { expression: "0 8 * * *", timezone: "America/Bogota" },
    ],
  },
  handler: async (_input, ctx) => {
    if (!ctx.isCron) return { skipped: true };

    ctx.log("Sending reminders", { cron: ctx.trigger.cron });
    const apiKey = ctx.env.get("JELOU_API_KEY");

    const res = await fetch("https://api.jelou.ai/v1/messages/send", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        botId: ctx.bot.id,
        phone: "593987654321",
        message: "Hi, we remind you that you have an appointment tomorrow at 10:00 AM.",
      }),
    });

    return { sent: 1, status: res.status };
  },
});

Local testing

Start the server with jelou dev. You can send an HTTP request to the function, but the isCron guard will reject it because it is not a real cron trigger:
curl -X POST http://localhost:3000 \
  -H "Content-Type: application/json" \
  -d '{}'
You cannot simulate a real cron trigger from curl — the platform injects ctx.isCron and the cryptographic signature automatically. To test cron logic, use createMockContext({ isCron: true }) in your tests.

Syntax

Each schedule has two fields:
FieldTypeRequiredDescription
expressionstringYesStandard 5-field cron
timezonestringNoIANA timezone. Default: UTC.

Expression format

┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * *

Common examples

ExpressionDescription
0 9 * * *Every day at 9:00 AM
0 9 * * 1-5Monday to Friday at 9:00 AM
*/30 * * * *Every 30 minutes
0 0 1 * *First day of each month at midnight
0 */2 * * *Every 2 hours
30 14 * * 3Wednesday at 2:30 PM

Timezones

Use any valid IANA zone:
config: {
  cron: [
    { expression: "0 9 * * *", timezone: "America/Guayaquil" },   // Ecuador
    { expression: "0 9 * * *", timezone: "America/Bogota" },      // Colombia
    { expression: "0 9 * * *", timezone: "America/Mexico_City" }, // Mexico
    { expression: "0 9 * * *", timezone: "America/Argentina/Buenos_Aires" },
    { expression: "0 3 * * *" },                                   // UTC by default
  ],
}

isCron guard

Your function can receive both HTTP requests and cron triggers. Use ctx.isCron to distinguish them:
handler: async (_input, ctx) => {
  if (!ctx.isCron) {
    return { skipped: true, reason: "not a cron trigger" };
  }

  ctx.log("Cron task running", { cron: ctx.trigger.cron });
  return { executed: true };
}
Without the isCron guard, any HTTP request to your function will execute the cron logic. Always include this check.

How it works

  1. You define the schedules in config.cron
  2. When you run jelou deploy, the platform reads your configuration and creates the schedules
  3. When a schedule fires, your function receives a request with ctx.isCron === true and ctx.trigger.cron with the expression that triggered it
  4. Cryptographic signature verification prevents unauthorized invocations

Limits

  • Maximum 10 cron schedules per function
  • Exceeding this limit throws an error at definition time

Management

Schedules are declarative — they are defined in code and synchronized on each deployment. To modify a schedule, change config.cron in your code and redeploy. To view active schedules:
jelou cron list query-customer
# ▸ Expression     Timezone             Enabled  Last Triggered
# ▸ 0 8 * * *     America/Guayaquil    yes      2 hours ago
# ▸ 0 8 * * *     America/Bogota       yes      2 hours ago

Multi-tool cron

When you use app(), each tool can have its own independent cron schedules. Cron requests are sent to the specific route of each tool.
import { app, define, z } from "@jelou/functions";

export default app({
  tools: {
    dailyCleanup: define({
      description: "Cleans up stale records",
      input: z.object({}),
      config: { cron: [{ expression: "0 3 * * *", timezone: "UTC" }] },
      handler: async (_input, ctx) => {
        if (!ctx.isCron) return { skipped: true };
        return { cleaned: true };
      },
    }),
    hourlySync: define({
      description: "Syncs external data",
      input: z.object({}),
      config: { cron: [{ expression: "0 * * * *" }] },
      handler: async (_input, ctx) => {
        if (!ctx.isCron) return { skipped: true };
        return { synced: true };
      },
    }),
  },
});
The 10 cron schedule limit is aggregated across all tools in an app(). If one tool uses 6 schedules, the other tools can only use 4 in total.

Common issues

Cron schedules are synchronized on deploy. If you changed the cron expression, you need to redeploy:
jelou deploy
Verify that the schedule is active:
jelou cron list my-function
# ▸ Expression     Timezone             Enabled  Last Triggered
# ▸ 0 8 * * *     America/Guayaquil    yes      2 hours ago
If the Enabled column shows no, check that the cron expression is valid.
See the full multi-tool guide for more details about app().