Scheduled Tasks
Run recurring queries on a cron schedule and deliver results via email, Slack, or webhook.
Scheduled tasks run natural language queries on a cron schedule and deliver results to configured recipients. Use them for daily reports, weekly digests, or real-time alerting via webhooks.
Prerequisites
ATLAS_SCHEDULER_ENABLED=true- An internal database (
DATABASE_URL) for task persistence - Authentication enabled (any mode except
none)
Task Definition
A scheduled task consists of a question, a cron schedule, and delivery configuration.
curl -X POST http://localhost:3001/api/v1/scheduled-tasks \
-H "Authorization: Bearer <key>" \
-H "Content-Type: application/json" \
-d '{
"name": "Daily revenue check",
"question": "What was yesterday total revenue?",
"cronExpression": "0 9 * * *",
"deliveryChannel": "email",
"recipients": [
{ "type": "email", "address": "team@example.com" }
]
}'| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Task name (1--200 chars) |
question | string | Yes | Natural language question (1--2000 chars) |
cronExpression | string | Yes | Standard 5-field cron expression |
deliveryChannel | string | No | email, slack, or webhook (default: webhook) |
recipients | array | No | Delivery destinations |
connectionId | string | No | Target datasource (default if omitted) |
approvalMode | string | No | auto or manual (default: auto) |
Cron Expression Format
Standard 5-field cron syntax:
minute hour day-of-month month day-of-week
0-59 0-23 1-31 1-12 0-6 (0=Sunday)| Expression | Schedule |
|---|---|
0 9 * * * | Daily at 9:00 AM |
0 9 * * 1-5 | Weekdays at 9:00 AM |
0 0 * * 1 | Every Monday at midnight |
0 9 1 * * | First of every month at 9:00 AM |
*/15 * * * * | Every 15 minutes |
Delivery Channels
Requires Resend for email delivery.
RESEND_API_KEY=re_...
ATLAS_EMAIL_FROM="Atlas <reports@example.com>" # Optional, default: Atlas <noreply@useatlas.dev>Recipient format:
{ "type": "email", "address": "user@example.com" }Slack
Requires the Slack integration to be configured.
{ "type": "slack", "channel": "#analytics" }For multi-workspace OAuth, include teamId:
{ "type": "slack", "channel": "#analytics", "teamId": "T0123456" }Webhook
Sends a JSON payload to an HTTPS endpoint.
{
"type": "webhook",
"url": "https://hooks.example.com/atlas",
"headers": { "X-Custom-Header": "value" }
}Webhook URLs must use HTTPS in production. Private/internal IPs (localhost, 127., 10., 172.16-31., 192.168., 169.254.*) are blocked. Sensitive headers (authorization, cookie) are filtered.
Execution Backends
The scheduler needs a mechanism to trigger periodic checks for due tasks.
bun (Default)
In-process tick loop using setInterval. No external dependencies.
ATLAS_SCHEDULER_BACKEND=bunBest for single-instance deployments. The tick loop starts automatically when the API server boots.
webhook
External cron service calls the tick endpoint. Multi-instance safe.
ATLAS_SCHEDULER_BACKEND=webhook
ATLAS_SCHEDULER_SECRET=your-random-secretConfigure your external cron to call:
curl -X POST https://api.example.com/api/v1/scheduled-tasks/tick \
-H "Authorization: Bearer $ATLAS_SCHEDULER_SECRET"vercel
Uses Vercel's built-in cron service. Configure in vercel.json:
{
"crons": [{
"path": "/api/v1/scheduled-tasks/tick",
"schedule": "*/5 * * * *"
}]
}ATLAS_SCHEDULER_BACKEND=vercel
# CRON_SECRET is auto-injected by VercelExecution Model
Each tick cycle:
- Fetch all enabled tasks where
next_run_at <= now() - Lock each task atomically (prevents double-execution across instances)
- Run up to
ATLAS_SCHEDULER_MAX_CONCURRENTtasks in parallel - For each task: execute the agent query, deliver results, log the run
- Recompute
next_run_atfrom the cron expression
Concurrency and Timeouts
| Setting | Env Var | Default | Description |
|---|---|---|---|
| Max concurrent | ATLAS_SCHEDULER_MAX_CONCURRENT | 5 | Max tasks per tick |
| Task timeout | ATLAS_SCHEDULER_TIMEOUT | 60000 | Per-task deadline (ms) |
| Tick interval | ATLAS_SCHEDULER_TICK_INTERVAL | 60 | Seconds between ticks (bun backend only) |
Failure Handling
- Task timeout -- Run marked as
failedwith timeout error - Agent error -- Run marked as
failed, error logged - Partial delivery failure -- If 1 of 3 recipients fails, the run is still
success. Delivery failures are logged separately - Lock contention -- Second instance skips the task (atomic locking)
API Endpoints
All routes require authentication and are available at /api/v1/scheduled-tasks.
| Method | Path | Description |
|---|---|---|
GET | / | List tasks (paginated, filterable by enabled) |
POST | / | Create a new task |
GET | /:id | Get task with recent runs |
PUT | /:id | Update task fields |
DELETE | /:id | Disable task (soft delete for audit trail) |
POST | /:id/run | Trigger immediate execution |
GET | /:id/runs | List past runs |
POST | /tick | Serverless tick (webhook/vercel backends) |
See the SDK for typed client methods.
Environment Variables
| Variable | Default | Description |
|---|---|---|
ATLAS_SCHEDULER_ENABLED | — | Set to true to enable |
ATLAS_SCHEDULER_BACKEND | bun | bun, webhook, or vercel |
ATLAS_SCHEDULER_MAX_CONCURRENT | 5 | Max concurrent tasks |
ATLAS_SCHEDULER_TIMEOUT | 60000 | Per-task timeout (ms) |
ATLAS_SCHEDULER_TICK_INTERVAL | 60 | Tick interval in seconds |
ATLAS_SCHEDULER_SECRET | — | Shared secret for /tick endpoint |
RESEND_API_KEY | — | Required for email delivery |
ATLAS_EMAIL_FROM | Atlas <noreply@useatlas.dev> | Email from address |
The scheduled tasks default email address (noreply@useatlas.dev) differs from the actions framework default (atlas@notifications.useatlas.dev). Set ATLAS_EMAIL_FROM to use a consistent address across both.
See Environment Variables for the full reference.