> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/Antony-Figueroa/my-evershop-app/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating Cron Jobs

> Learn how to create scheduled background tasks in EverShop

Cron jobs allow you to schedule background tasks to run automatically at specified intervals. They're perfect for maintenance tasks, data synchronization, sending scheduled emails, generating reports, and cleaning up old data.

## Overview

Cron jobs in EverShop:

* **Run automatically** on a schedule you define
* **Use cron syntax** for flexible scheduling
* **Execute in the background** without blocking the application
* **Are registered in bootstrap.ts** during application startup
* **Support async/await** for modern JavaScript patterns

## How Cron Jobs Work

```
Application Starts → Bootstrap Registers Jobs → Jobs Run on Schedule
```

1. Extension's `bootstrap.ts` registers cron jobs
2. Jobs are scheduled using cron expressions
3. Jobs execute automatically at specified times
4. Output is logged to console

## Directory Structure

Cron jobs are organized in the `crons` folder:

```
extensions/[extension-name]/
├── src/
│   ├── crons/
│   │   ├── everyMinute.ts
│   │   ├── dailyCleanup.ts
│   │   └── weeklyReport.ts
│   └── bootstrap.ts
└── package.json
```

## Creating Your First Cron Job

<Steps>
  ### Create the Cron Function

  Create a file in the `crons` directory:

  ```typescript title="src/crons/everyMinute.ts" theme={null}
  export default function EveryMinute() {
    console.log('This cron job runs every minute');
  }
  ```

  <Note>
    The function name and file name can be anything descriptive.
  </Note>

  ### Register in Bootstrap

  Register the job in your extension's `bootstrap.ts`:

  ```typescript title="src/bootstrap.ts" theme={null}
  import path from 'path';
  import { registerJob } from '@evershop/evershop/lib/cronjob';

  export default function () {
    registerJob({
      name: 'everyMinuteJob',
      schedule: '*/1 * * * *',  // Runs every minute
      resolve: path.resolve(import.meta.dirname, 'crons', 'everyMinute.js'),
      enabled: true
    });
  }
  ```

  ### Build and Run

  Build your extension:

  ```bash theme={null}
  npm run build
  npm run dev
  ```

  The cron job will now run automatically based on the schedule.
</Steps>

## Real-World Example

Here's the actual example from the sample extension:

### The Cron Function

```typescript title="extensions/sample/src/crons/everyMinute.ts" theme={null}
export default function EveryMinute() {
  console.log('This cron job runs every minute');
}
```

### The Bootstrap Registration

```typescript title="extensions/sample/src/bootstrap.ts" theme={null}
import path from 'path';
import { registerJob } from '@evershop/evershop/lib/cronjob';

export default function () {
  registerJob({
    name: 'sampleJob',
    schedule: '*/1 * * * *', // Runs every minute
    resolve: path.resolve(import.meta.dirname, 'crons', 'everyMinute.js'),
    enabled: true
  });
}
```

## Cron Schedule Syntax

Cron jobs use standard cron syntax with 5 fields:

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

### Common Schedules

| Schedule              | Expression     | Description                    |
| --------------------- | -------------- | ------------------------------ |
| Every minute          | `* * * * *`    | Runs every minute              |
| Every 5 minutes       | `*/5 * * * *`  | Runs every 5 minutes           |
| Every hour            | `0 * * * *`    | Runs at minute 0 of every hour |
| Every day at midnight | `0 0 * * *`    | Runs at 00:00 every day        |
| Every day at 3 AM     | `0 3 * * *`    | Runs at 03:00 every day        |
| Every Monday at 9 AM  | `0 9 * * 1`    | Runs Mondays at 09:00          |
| First day of month    | `0 0 1 * *`    | Runs at midnight on the 1st    |
| Weekdays at 9 AM      | `0 9 * * 1-5`  | Runs Mon-Fri at 09:00          |
| Every 15 minutes      | `*/15 * * * *` | Runs every 15 minutes          |

<Tip>
  Use [crontab.guru](https://crontab.guru) to help build and understand cron expressions.
</Tip>

## Advanced Examples

### Daily Database Cleanup

```typescript title="src/crons/dailyCleanup.ts" theme={null}
import { pool } from '@evershop/evershop/lib/postgres/connection';

export default async function DailyCleanup() {
  console.log('Starting daily cleanup...');

  try {
    // Delete old sessions (older than 30 days)
    await pool.query(
      `DELETE FROM sessions WHERE updated_at < NOW() - INTERVAL '30 days'`
    );

    // Delete expired carts (older than 7 days)
    await pool.query(
      `DELETE FROM carts 
       WHERE status = 'abandoned' 
       AND updated_at < NOW() - INTERVAL '7 days'`
    );

    // Clean up old log entries
    await pool.query(
      `DELETE FROM audit_log WHERE created_at < NOW() - INTERVAL '90 days'`
    );

    console.log('Daily cleanup completed successfully');
  } catch (error) {
    console.error('Daily cleanup failed:', error);
  }
}
```

```typescript title="src/bootstrap.ts" theme={null}
registerJob({
  name: 'dailyCleanup',
  schedule: '0 3 * * *',  // 3 AM every day
  resolve: path.resolve(import.meta.dirname, 'crons', 'dailyCleanup.js'),
  enabled: true
});
```

### Weekly Sales Report

```typescript title="src/crons/weeklySalesReport.ts" theme={null}
import { pool } from '@evershop/evershop/lib/postgres/connection';
import { sendEmail } from '../services/emailService';

export default async function WeeklySalesReport() {
  console.log('Generating weekly sales report...');

  try {
    // Get sales data for the past week
    const result = await pool.query(
      `SELECT 
         COUNT(*) as total_orders,
         SUM(grand_total) as total_revenue,
         AVG(grand_total) as average_order_value
       FROM orders
       WHERE created_at >= NOW() - INTERVAL '7 days'
       AND status = 'completed'`
    );

    const stats = result.rows[0];

    // Get top selling products
    const topProducts = await pool.query(
      `SELECT 
         p.name,
         SUM(oi.quantity) as quantity_sold,
         SUM(oi.total) as revenue
       FROM order_items oi
       JOIN products p ON oi.product_id = p.product_id
       JOIN orders o ON oi.order_id = o.order_id
       WHERE o.created_at >= NOW() - INTERVAL '7 days'
       AND o.status = 'completed'
       GROUP BY p.product_id, p.name
       ORDER BY quantity_sold DESC
       LIMIT 10`
    );

    // Send email report
    await sendEmail({
      to: 'admin@mystore.com',
      subject: 'Weekly Sales Report',
      template: 'weekly-report',
      data: {
        totalOrders: stats.total_orders,
        totalRevenue: stats.total_revenue,
        averageOrderValue: stats.average_order_value,
        topProducts: topProducts.rows
      }
    });

    console.log('Weekly sales report sent successfully');
  } catch (error) {
    console.error('Failed to generate weekly report:', error);
  }
}
```

```typescript title="src/bootstrap.ts" theme={null}
registerJob({
  name: 'weeklySalesReport',
  schedule: '0 9 * * 1',  // Every Monday at 9 AM
  resolve: path.resolve(import.meta.dirname, 'crons', 'weeklySalesReport.js'),
  enabled: true
});
```

### Inventory Sync

```typescript title="src/crons/syncInventory.ts" theme={null}
import fetch from 'node-fetch';
import { pool } from '@evershop/evershop/lib/postgres/connection';

export default async function SyncInventory() {
  console.log('Syncing inventory with warehouse system...');

  try {
    // Fetch inventory data from external system
    const response = await fetch(
      `${process.env.WAREHOUSE_API_URL}/inventory`,
      {
        headers: {
          'Authorization': `Bearer ${process.env.WAREHOUSE_API_KEY}`
        }
      }
    );

    if (!response.ok) {
      throw new Error(`Warehouse API returned ${response.status}`);
    }

    const inventoryData = await response.json();

    // Update inventory in database
    for (const item of inventoryData) {
      await pool.query(
        `UPDATE products 
         SET stock_quantity = $1, updated_at = NOW()
         WHERE sku = $2`,
        [item.quantity, item.sku]
      );
    }

    console.log(`Synced inventory for ${inventoryData.length} products`);
  } catch (error) {
    console.error('Inventory sync failed:', error);
  }
}
```

```typescript title="src/bootstrap.ts" theme={null}
registerJob({
  name: 'syncInventory',
  schedule: '*/30 * * * *',  // Every 30 minutes
  resolve: path.resolve(import.meta.dirname, 'crons', 'syncInventory.js'),
  enabled: true
});
```

### Abandoned Cart Recovery

```typescript title="src/crons/abandonedCartReminder.ts" theme={null}
import { pool } from '@evershop/evershop/lib/postgres/connection';
import { sendEmail } from '../services/emailService';

export default async function AbandonedCartReminder() {
  console.log('Checking for abandoned carts...');

  try {
    // Find carts abandoned 24 hours ago
    const result = await pool.query(
      `SELECT 
         c.cart_id,
         c.customer_email,
         c.items,
         c.total
       FROM carts c
       WHERE c.status = 'active'
       AND c.updated_at BETWEEN NOW() - INTERVAL '25 hours' 
                           AND NOW() - INTERVAL '24 hours'
       AND c.customer_email IS NOT NULL
       AND NOT EXISTS (
         SELECT 1 FROM cart_recovery_emails 
         WHERE cart_id = c.cart_id
       )`
    );

    for (const cart of result.rows) {
      // Send recovery email
      await sendEmail({
        to: cart.customer_email,
        subject: 'You left items in your cart!',
        template: 'cart-recovery',
        data: {
          items: cart.items,
          total: cart.total,
          cartLink: `https://mystore.com/cart/${cart.cart_id}`
        }
      });

      // Log that we sent the email
      await pool.query(
        `INSERT INTO cart_recovery_emails (cart_id, sent_at) 
         VALUES ($1, NOW())`,
        [cart.cart_id]
      );
    }

    console.log(`Sent ${result.rows.length} cart recovery emails`);
  } catch (error) {
    console.error('Cart recovery failed:', error);
  }
}
```

```typescript title="src/bootstrap.ts" theme={null}
registerJob({
  name: 'abandonedCartReminder',
  schedule: '0 */6 * * *',  // Every 6 hours
  resolve: path.resolve(import.meta.dirname, 'crons', 'abandonedCartReminder.js'),
  enabled: true
});
```

## Registration Options

The `registerJob` function accepts these options:

```typescript theme={null}
registerJob({
  name: 'myJob',              // Unique job name
  schedule: '*/5 * * * *',    // Cron expression
  resolve: path.resolve(...), // Path to job file
  enabled: true               // Enable/disable the job
});
```

### Dynamic Enable/Disable

Use environment variables to control jobs:

```typescript theme={null}
registerJob({
  name: 'expensiveJob',
  schedule: '0 2 * * *',
  resolve: path.resolve(import.meta.dirname, 'crons', 'expensiveJob.js'),
  enabled: process.env.NODE_ENV === 'production'
});
```

## Multiple Cron Jobs

Register multiple jobs in the same bootstrap:

```typescript title="src/bootstrap.ts" theme={null}
import path from 'path';
import { registerJob } from '@evershop/evershop/lib/cronjob';

export default function () {
  // Daily cleanup at 3 AM
  registerJob({
    name: 'dailyCleanup',
    schedule: '0 3 * * *',
    resolve: path.resolve(import.meta.dirname, 'crons', 'dailyCleanup.js'),
    enabled: true
  });

  // Hourly inventory sync
  registerJob({
    name: 'syncInventory',
    schedule: '0 * * * *',
    resolve: path.resolve(import.meta.dirname, 'crons', 'syncInventory.js'),
    enabled: true
  });

  // Weekly report on Mondays
  registerJob({
    name: 'weeklyReport',
    schedule: '0 9 * * 1',
    resolve: path.resolve(import.meta.dirname, 'crons', 'weeklyReport.js'),
    enabled: true
  });
}
```

## Best Practices

<Tip>
  **Error Handling**: Always wrap cron job logic in try-catch blocks to prevent crashes.
</Tip>

* **Logging** - Log start, success, and failure of each job
* **Error Handling** - Use try-catch to handle errors gracefully
* **Idempotency** - Design jobs to be safe to run multiple times
* **Timeouts** - Set reasonable timeouts for external API calls
* **Performance** - Optimize queries and batch operations
* **Monitoring** - Track job execution and failures
* **Testing** - Test jobs in development before production

## Testing Cron Jobs

### Manual Execution

Create a test script to run jobs manually:

```typescript title="scripts/testCron.ts" theme={null}
import myJob from '../extensions/my-extension/src/crons/myJob';

// Run the job
await myJob();
console.log('Job completed');
```

Run it:

```bash theme={null}
ts-node scripts/testCron.ts
```

### Development Testing

Use a short interval for testing:

```typescript theme={null}
// For development
registerJob({
  name: 'testJob',
  schedule: '*/1 * * * *',  // Every minute
  resolve: path.resolve(import.meta.dirname, 'crons', 'testJob.js'),
  enabled: process.env.NODE_ENV === 'development'
});
```

## Troubleshooting

### Job Not Running

1. Verify job is registered in `bootstrap.ts`
2. Check `enabled` is set to `true`
3. Verify cron expression syntax
4. Check application is running: `npm run dev`
5. Look for errors in console logs

### Job Running Multiple Times

1. Ensure job name is unique
2. Check you're not registering the same job twice
3. Verify only one instance of the app is running

### Job Errors

1. Add try-catch blocks
2. Check database connections
3. Verify external API credentials
4. Test the job function in isolation

## Common Use Cases

### Maintenance Tasks

* Database cleanup
* Log rotation
* Cache invalidation
* Session cleanup

### Data Synchronization

* Inventory updates
* Price synchronization
* Product imports
* Order exports

### Reporting

* Daily sales reports
* Weekly analytics
* Monthly summaries
* Performance metrics

### Customer Engagement

* Abandoned cart emails
* Re-engagement campaigns
* Birthday emails
* Product recommendations

## Next Steps

<CardGroup cols={2}>
  <Card title="Event Subscribers" icon="bell" href="/guides/event-subscribers">
    React to real-time events
  </Card>

  <Card title="Creating Extensions" icon="puzzle-piece" href="/guides/creating-extensions">
    Learn extension architecture
  </Card>
</CardGroup>
