Cron Scheduling
Schedule recurring tasks with Elysia's cron plugin
Cron Scheduling
Eden Stack supports scheduled tasks using the @elysiajs/cron plugin, powered by Croner. Cron jobs run in-process alongside your Elysia API server.
When to Use Cron vs Inngest
| Feature | Elysia Cron | Inngest Scheduled |
|---|---|---|
| Persistence | In-memory only | Durable, survives restarts |
| Retry on failure | No built-in retry | Automatic retries |
| Monitoring | Manual logging | Dashboard + history |
| Setup complexity | Minimal | Requires Inngest account |
| Best for | Dev tools, health checks, lightweight tasks | Critical business logic, billing, reports |
Rule of thumb: Use Elysia cron for lightweight, non-critical tasks. Use Inngest cron for anything that must not be missed.
Setup
Install the plugin in your API app:
bun add @elysiajs/cron --filter=@eden/apiBasic Usage
import { cron, Patterns } from "@elysiajs/cron";
import { Elysia } from "elysia";
const app = new Elysia()
.use(
cron({
name: "heartbeat",
pattern: Patterns.EVERY_MINUTE,
run() {
console.log("Server is alive", new Date().toISOString());
},
})
)
.listen(3001);Cron Expression Syntax
┌────────────── second (optional)
│ ┌──────────── minute
│ │ ┌────────── hour
│ │ │ ┌──────── day of month
│ │ │ │ ┌────── month
│ │ │ │ │ ┌──── day of week
│ │ │ │ │ │
* * * * * *Common expressions:
| Expression | Schedule |
|---|---|
0 */5 * * * | Every 5 minutes |
0 0 9 * * * | Daily at 9:00 AM |
0 0 0 * * 1 | Every Monday at midnight |
0 0 1 1 * | Yearly on Jan 1st |
Predefined Patterns
The plugin ships with readable pattern helpers:
import { Patterns } from "@elysiajs/cron";
Patterns.EVERY_MINUTE
Patterns.EVERY_HOUR
Patterns.EVERY_DAY_AT_MIDNIGHT
Patterns.EVERY_WEEK
Patterns.EVERY_WEEKDAY
// Dynamic patterns
Patterns.everyMinutes(5)
Patterns.everyDayAt("09:00")
Patterns.everyWeekOn(Patterns.MONDAY, "09:00")
Patterns.everyWeekdayAt("17:00")Configuration Options
cron({
name: "my-job", // Required — registered in store.cron
pattern: "0 9 * * *", // Required — cron expression
timezone: "America/New_York", // Optional — defaults to system
catch: true, // Optional — continue on error
maxRuns: 100, // Optional — stop after N executions
startAt: new Date("2025-06-01"), // Optional — delay start
stopAt: new Date("2025-12-31"), // Optional — auto-stop
run() {
// Your job logic
},
});Controlling Jobs at Runtime
Jobs are accessible through store.cron:
const app = new Elysia()
.use(
cron({
name: "reports",
pattern: Patterns.everyDayAt("09:00"),
run() { generateReport(); },
})
)
.get("/cron/stop/:name", ({ store, params }) => {
store.cron[params.name]?.stop();
return { stopped: params.name };
})
.get("/cron/trigger/:name", ({ store, params }) => {
store.cron[params.name]?.trigger();
return { triggered: params.name };
});Available methods on each job:
.stop()— Pause the job.resume()— Resume a paused job.trigger()— Run immediately.nextRun()— Next scheduled execution time.previousRun()— Last execution time
SaaS Use Cases
Here are practical examples of cron jobs you might add to your SaaS:
Trial Expiration Reminders
cron({
name: "trial-expiration-check",
pattern: Patterns.everyDayAt("08:00"),
timezone: "UTC",
catch: true,
async run() {
const expiringSoon = await db
.select()
.from(users)
.where(
and(
eq(users.subscriptionStatus, "trialing"),
lte(users.trialEndsAt, addDays(new Date(), 3))
)
);
for (const user of expiringSoon) {
await sendEmail({
to: user.email,
subject: "Your trial expires soon",
template: TrialExpiringEmail({ name: user.name }),
});
}
},
});Stale Data Cleanup
cron({
name: "cleanup-expired-sessions",
pattern: Patterns.EVERY_HOUR,
catch: true,
async run() {
const deleted = await db
.delete(sessions)
.where(lt(sessions.expiresAt, new Date()));
console.log(`[CRON] Cleaned up ${deleted.rowCount} expired sessions`);
},
});Usage Metrics Aggregation
cron({
name: "aggregate-daily-metrics",
pattern: Patterns.everyDayAt("00:05"),
timezone: "UTC",
catch: true,
async run() {
const yesterday = subDays(new Date(), 1);
const metrics = await db
.select({
workspaceId: apiCalls.workspaceId,
count: count(),
})
.from(apiCalls)
.where(gte(apiCalls.createdAt, yesterday))
.groupBy(apiCalls.workspaceId);
await db.insert(dailyUsage).values(
metrics.map((m) => ({
workspaceId: m.workspaceId,
apiCallCount: m.count,
date: yesterday,
}))
);
},
});Weekly Digest Email
cron({
name: "weekly-digest",
pattern: Patterns.everyWeekOn(Patterns.MONDAY, "09:00"),
timezone: "UTC",
catch: true,
async run() {
const activeUsers = await db
.select()
.from(users)
.where(eq(users.digestEnabled, true));
for (const user of activeUsers) {
const stats = await getWeeklyStats(user.id);
await sendEmail({
to: user.email,
subject: "Your Weekly Summary",
template: WeeklyDigestEmail({ name: user.name, stats }),
});
}
},
});Health Check Ping
cron({
name: "external-health-check",
pattern: Patterns.everyMinutes(5),
catch: true,
async run() {
const services = ["https://api.stripe.com", "https://api.resend.com"];
for (const url of services) {
try {
const res = await fetch(url, { method: "HEAD" });
if (!res.ok) console.warn(`[HEALTH] ${url} returned ${res.status}`);
} catch (err) {
console.error(`[HEALTH] ${url} unreachable:`, err);
}
}
},
});Organizing Cron Jobs
Keep cron jobs in a dedicated route file:
apps/api/src/
├── routes/
│ ├── cron.ts # All cron job definitions
│ ├── payments.ts
│ └── ...
└── index.ts # .use(cronRoutes)For many jobs, split into separate files:
apps/api/src/
├── cron/
│ ├── index.ts # Combines all cron plugins
│ ├── cleanup.ts # Data cleanup jobs
│ ├── notifications.ts # Email/push notification jobs
│ └── metrics.ts # Analytics aggregation jobsDemo Job
Eden Stack includes a demo cron job at apps/api/src/routes/cron.ts that sends a weekly email every Monday at 09:00 UTC. It uses the CronDemoEmail template from @eden/email.
Check cron status:
curl http://localhost:3001/api/cron/statusSet CRON_DEMO_RECIPIENT in your .env to receive the demo email, or remove the job once confirmed.
Next Steps
- Background Jobs — Durable jobs with Inngest
- Stripe Payments — Webhook processing
- Packages — Explore all packages
Full documentation for Eden Stack users
This documentation is exclusively available to Eden Stack customers. Already purchased? Log in to access the full content.