Cron Jobs & Scheduled Tasks

Temps includes a built-in cron scheduler that lets you run recurring tasks by invoking HTTP endpoints on a schedule. Define your cron jobs in your repository's .temps.yaml file—no external services needed.


Quick Start

Step 1: Create .temps.yaml File

Create a .temps.yaml file in your repository root:

cron:
  - path: "/api/cleanup"
    schedule: "0 * * * *"
    name: "Hourly Cleanup"

Step 2: Deploy Your Application

When you deploy, Temps automatically:

  • Reads your .temps.yaml file
  • Configures the cron jobs
  • Starts scheduling them

Step 3: Create Your Endpoint

Create the endpoint that will be invoked:

// app/api/cleanup/route.ts (Next.js)
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  // Verify cron secret (recommended)
  const authHeader = request.headers.get('authorization');
  if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // Perform cleanup task
  await cleanupOldRecords();
  await archiveExpiredSessions();

  return NextResponse.json({
    success: true,
    message: 'Cleanup completed',
    timestamp: new Date().toISOString()
  });
}

That's it! Your cron job will run automatically on the schedule you defined.


Configuration File

Cron jobs are defined in a .temps.yaml file in your project directory.

File Location

The .temps.yaml file should be placed in your project's root directory. For most projects, this is the repository root:

your-repo/
  ├── .temps.yaml          ← Place here (repository root)
  ├── src/
  ├── package.json
  └── ...

For monorepos, if your project is in a subdirectory, place .temps.yaml in that subdirectory:

monorepo/
  ├── apps/
  │   └── my-app/
  │       ├── .temps.yaml  ← Place here (project directory)
  │       ├── src/
  │       └── package.json
  └── packages/

The file location matches your project's configured directory in Temps.

Basic Format

cron:
  - path: "/api/cleanup"
    schedule: "0 * * * *"
    name: "Hourly Cleanup"  # Optional: descriptive name

Multiple Cron Jobs

cron:
  - path: "/api/cleanup"
    schedule: "0 * * * *"
    name: "Hourly Cleanup"

  - path: "/api/backup"
    schedule: "0 2 * * *"
    name: "Daily Backup"

  - path: "/api/digest"
    schedule: "0 9 * * *"
    name: "Daily Email Digest"

Full Configuration Example

# .temps.yaml
cron:
  - path: "/api/cleanup"
    schedule: "0 * * * *"
    name: "Hourly Cleanup"

  - path: "/api/backup"
    schedule: "0 2 * * *"
    name: "Daily Backup at 2 AM"

  - path: "/api/weekly-report"
    schedule: "0 9 * * 1"
    name: "Weekly Report on Mondays"

# Other configuration options
build:
  dockerfile: Dockerfile

env:
  NODE_ENV: production

How It Works

  1. Add .temps.yaml to your project directory (repository root or subdirectory for monorepos)
  2. Commit and push to Git
  3. Deploy your application
  4. After deployment completes, Temps reads the .temps.yaml file
  5. Cron jobs are configured automatically for that environment
  6. Jobs run on the specified schedule

Cron Expression Syntax

Temps uses standard cron expression format with 5 fields:

┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday)
│ │ │ │ │
│ │ │ │ │
* * * * *

Common Schedules

  • Name
    Every 5 minutes
    Description
    schedule: "*/5 * * * *"
    
  • Name
    Every hour at minute 0
    Description
    schedule: "0 * * * *"
    
  • Name
    Daily at 2:30 AM
    Description
    schedule: "30 2 * * *"
    
  • Name
    Weekly on Monday at 9 AM
    Description
    schedule: "0 9 * * 1"
    
  • Name
    Monthly on the 1st at midnight
    Description
    schedule: "0 0 1 * *"
    
  • Name
    Every weekday at 8 AM
    Description
    schedule: "0 8 * * 1-5"
    
  • Name
    Twice daily (6 AM and 6 PM)
    Description
    schedule: "0 6,18 * * *"
    

Endpoint Invocation

HTTP Method

Temps invokes your endpoints using GET requests by default.

Authentication

Temps automatically includes an Authorization header when invoking cron endpoints:

Authorization: Bearer <TEMPS_CRON_SECRET>

Set your cron secret as an environment variable:

# In Temps dashboard: Project Settings > Environment Variables
CRON_SECRET=your-secret-key-here

Verify in your endpoint:

Verify Cron Secret

// app/api/cleanup/route.ts
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  // Verify cron secret
  const authHeader = request.headers.get('authorization');
  if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // Perform cleanup task
  await cleanupOldRecords();

  return NextResponse.json({ success: true });
}

Per-Environment Scheduling

Cron jobs defined in .temps.yaml are configured per environment when you deploy. This means:

  • Same .temps.yaml file - The file is shared across all environments
  • Environment-specific execution - Each environment (dev, staging, production) runs its own cron jobs
  • Isolated execution - Production and staging crons don't interfere with each other
  • Configured after deployment - Cron jobs are set up automatically after each deployment completes

Example: Different Behavior Per Environment

While the schedule is the same, you can make your endpoint behave differently based on environment:

// app/api/backup/route.ts
export async function GET(request: Request) {
  const env = process.env.NODE_ENV;

  if (env === 'production') {
    // Production: Full backup every 6 hours
    await performFullBackup();
  } else {
    // Staging: Lightweight backup daily
    await performLightBackup();
  }

  return Response.json({ success: true });
}
# .temps.yaml
cron:
  - path: "/api/backup"
    schedule: "0 */6 * * *"  # Every 6 hours (production will do full backup)
    name: "Database Backup"

Common Use Cases

1. Database Backups

Schedule regular database backups:

# .temps.yaml
cron:
  - path: "/api/backup"
    schedule: "0 2 * * *"  # Daily at 2 AM
    name: "Database Backup"
// app/api/backup/route.ts
export async function GET(request: Request) {
  // Verify auth...
  
  const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3');
  
  // Dump database
  const dump = await dumpDatabase();
  
  // Upload to S3
  const s3 = new S3Client({ /* config */ });
  await s3.send(new PutObjectCommand({
    Bucket: 'backups',
    Key: `database-${Date.now()}.sql`,
    Body: dump
  }));
  
  return Response.json({ success: true });
}

2. Data Cleanup

Remove old records, expired sessions, or temporary files:

# .temps.yaml
cron:
  - path: "/api/cleanup"
    schedule: "0 3 * * *"  # Daily at 3 AM
    name: "Daily Cleanup"
// app/api/cleanup/route.ts
export async function GET(request: Request) {
  // Verify auth...
  
  const sevenDaysAgo = new Date();
  sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
  
  // Delete expired sessions
  await db.session.deleteMany({
    where: { expiresAt: { lt: sevenDaysAgo } }
  });
  
  return Response.json({ success: true });
}

3. Email Digests

Send daily/weekly email summaries:

# .temps.yaml
cron:
  - path: "/api/digest"
    schedule: "0 9 * * *"  # Daily at 9 AM
    name: "Daily Email Digest"

4. Cache Warming

Pre-populate caches before peak traffic:

# .temps.yaml
cron:
  - path: "/api/warm-cache"
    schedule: "0 6 * * *"  # Daily at 6 AM
    name: "Cache Warming"

5. API Sync

Synchronize data from external APIs:

# .temps.yaml
cron:
  - path: "/api/sync"
    schedule: "*/15 * * * *"  # Every 15 minutes
    name: "External API Sync"

Monitoring & Logs

View Cron Jobs

In the Temps dashboard:

  1. Go to Project SettingsCron Jobs
  2. See all configured cron jobs
  3. View execution history
  4. Check success/failure rates

Execution Details

Each execution includes:

  • Start Time - When the job began
  • Duration - How long it took
  • Status - Success, Failed, or Timeout
  • HTTP Status - Response code from endpoint
  • Response Body - Endpoint response (first 1KB)
  • Error Message - If the job failed

View Logs

Check your application logs to see cron job execution:

# View deployment logs
temps logs --project my-app --environment production

# Filter for cron-related logs
temps logs --project my-app | grep "cron"

Error Handling

Retries

Failed cron jobs are automatically retried:

  • 1st retry: After 1 minute
  • 2nd retry: After 5 minutes
  • 3rd retry: After 15 minutes

After 3 failed attempts, the job is marked as failed.

Handling Failures in Code

Return appropriate status codes:

// app/api/cron/task/route.ts
export async function GET(request: Request) {
  try {
    await performTask();
    return Response.json({ success: true }, { status: 200 });
  } catch (error) {
    if (error instanceof TransientError) {
      // Will be retried (5xx errors)
      return Response.json({ error: error.message }, { status: 500 });
    } else {
      // Won't be retried (4xx errors)
      return Response.json({ error: error.message }, { status: 400 });
    }
  }
}

Timeout

Cron jobs have a default timeout of 5 minutes. If your job takes longer, it will be terminated.

For long-running tasks, consider:

  • Breaking them into smaller jobs
  • Using background job queues
  • Processing in batches

Best Practices

  • Name
    Idempotency
    Description

    Design cron jobs to be idempotent—running them multiple times should produce the same result:

    // ✅ Good: Idempotent
    await db.session.deleteMany({
      where: { expiresAt: { lt: sevenDaysAgo } }
    });
    
    // ❌ Bad: Not idempotent
    await db.user.updateMany({
      data: { credits: { increment: 10 } }
    });
    
  • Name
    Use Locking
    Description

    Prevent concurrent executions with distributed locks:

    import { redis } from '@/lib/redis';
    
    const lockKey = 'cron:backup:lock';
    const acquired = await redis.set(lockKey, '1', 'EX', 3600, 'NX');
    
    if (!acquired) {
      return Response.json({ error: 'Already running' }, { status: 409 });
    }
    
    try {
      await performBackup();
    } finally {
      await redis.del(lockKey);
    }
    
  • Name
    Monitor Duration
    Description

    Track how long jobs take and log if they exceed expected duration:

    const start = Date.now();
    await performTask();
    const duration = Date.now() - start;
    
    if (duration > 5 * 60 * 1000) { // 5 minutes
      console.warn(`Job took ${duration}ms`);
    }
    
  • Name
    Test Locally
    Description

    Test cron endpoints locally before deploying:

    # Invoke endpoint manually
    curl http://localhost:3000/api/cleanup \
      -H "Authorization: Bearer your-cron-secret"
    

Troubleshooting

  • Name
    Cron not executing
    Description

    Possible causes:

    • Invalid cron expression (use crontab.guru)
    • .temps.yaml file not in correct location (should match project directory)
    • Endpoint path doesn't match
    • Environment not deployed
    • YAML syntax error in .temps.yaml

    Solution:

    • Verify .temps.yaml is in your project directory (check project settings for directory path)
    • Check YAML syntax is valid
    • Check cron syntax with crontab.guru
    • Ensure your endpoint path matches exactly (including leading slash)
    • Redeploy your application (cron configuration happens after deployment)
    • Check deployment logs for cron configuration messages
  • Name
    401 Unauthorized errors
    Description

    Cause: Missing or incorrect CRON_SECRET environment variable.

    Solution:

    • Set CRON_SECRET in your environment variables
    • Verify it matches what your endpoint expects
    • Check the Authorization header format
  • Name
    Timeout errors
    Description

    Cause: Job takes longer than 5-minute default timeout.

    Solution:

    • Break long-running tasks into smaller jobs
    • Use background job queues for heavy processing
    • Process data in batches
  • Name
    Jobs running multiple times
    Description

    Cause: Multiple environments deployed with same schedule.

    Solution: This is expected behavior—each environment runs its own cron jobs. Use locking (see Best Practices) if you need to prevent concurrent execution.


Next Steps

Was this page helpful?