Configuração
Defina schedules de cron diretamente no config da sua função. Nenhuma configuração externa é necessária.
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 };
},
});
Testes locais
Inicie o servidor com jelou dev. Você pode enviar uma requisição HTTP para a função, mas o guard isCron a rejeitará porque não é um trigger de cron real:
curl -X POST http://localhost:3000 \
-H "Content-Type: application/json" \
-d '{}'
Não é possível simular um trigger de cron real via curl — a plataforma injeta ctx.isCron e a assinatura criptográfica automaticamente. Para testar a lógica de cron, use createMockContext({ isCron: true }) nos seus testes.
Sintaxe
Cada schedule tem dois campos:
| Campo | Tipo | Obrigatório | Descrição |
|---|
expression | string | Sim | Cron padrão de 5 campos |
timezone | string | Não | Fuso horário IANA. Padrão: UTC. |
┌───────────── minuto (0-59)
│ ┌───────────── hora (0-23)
│ │ ┌───────────── dia do mês (1-31)
│ │ │ ┌───────────── mês (1-12)
│ │ │ │ ┌───────────── dia da semana (0-7, 0 e 7 = domingo)
│ │ │ │ │
* * * * *
Exemplos comuns
| Expressão | Descrição |
|---|
0 9 * * * | Todo dia às 9:00 |
0 9 * * 1-5 | Segunda a sexta às 9:00 |
*/30 * * * * | A cada 30 minutos |
0 0 1 * * | Primeiro dia de cada mês à meia-noite |
0 */2 * * * | A cada 2 horas |
30 14 * * 3 | Quarta-feira às 14:30 |
Fusos horários
Use qualquer zona IANA válida:
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
],
}
Guard isCron
Sua função pode receber tanto requisições HTTP quanto triggers de cron. Use ctx.isCron para diferenciá-los:
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 };
}
Sem o guard isCron, qualquer requisição HTTP para sua função executará a lógica de cron. Sempre inclua essa verificação.
Como funciona
- Você define os schedules em
config.cron
- Quando você executa
jelou deploy, a plataforma lê sua configuração e cria os schedules
- Quando um schedule dispara, sua função recebe uma requisição com
ctx.isCron === true e ctx.trigger.cron com a expressão que a acionou
- A verificação de assinatura criptográfica impede invocações não autorizadas
Limites
- Máximo de 10 schedules de cron por função
- Exceder esse limite gera um erro em tempo de definição
Gerenciamento
Os schedules são declarativos — são definidos no código e sincronizados a cada deploy. Para modificar um schedule, altere config.cron no seu código e faça o redeploy.
Para visualizar os schedules ativos:
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
Quando você usa app(), cada ferramenta pode ter seus próprios schedules de cron independentes. As requisições de cron são enviadas para a rota específica de cada ferramenta.
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 };
},
}),
},
});
O limite de 10 schedules de cron é agregado entre todas as ferramentas de um app(). Se uma ferramenta usar 6 schedules, as demais ferramentas só poderão usar 4 no total.
Problemas comuns
Os schedules de cron são sincronizados no deploy. Se você alterou a expressão cron, precisa fazer o redeploy:Verifique se o schedule está ativo:jelou cron list my-function
# ▸ Expression Timezone Enabled Last Triggered
# ▸ 0 8 * * * America/Guayaquil yes 2 hours ago
Se a coluna Enabled mostrar no, verifique se a expressão cron é válida. Sem o guard isCron, qualquer requisição HTTP executará a lógica de cron. Adicione a verificação no início do handler:handler: async (_input, ctx) => {
if (!ctx.isCron) return { skipped: true };
// your cron logic here
return { executed: true };
}
Isso retorna { skipped: true } para requisições HTTP normais e só executa a lógica quando é um trigger de cron real.