Skip to main content
Preview
Preview Feature — This feature is currently in preview and under active development. APIs and functionality may change. We recommend testing thoroughly before using in production.

Job Management

Learn how to monitor, track, pause, resume, and cancel scheduled jobs in Ductape.

Job Lifecycle

Jobs in Ductape go through several states during their lifecycle:

scheduled → queued → running → completed
↘ ↘
failed cancelled

Job States

StateDescription
scheduledJob is scheduled but not yet queued for execution
queuedJob is in the execution queue waiting to run
runningJob is currently executing
completedJob finished successfully
failedJob failed after all retry attempts
cancelledJob was manually cancelled
pausedRecurring job is paused and won't run until resumed

Fetching Jobs

Get a Specific Job

// Fetch job by ID
const job = await ductape.jobs.get('job_abc123');

console.log('Job ID:', job.id);
console.log('Status:', job.status);
console.log('Scheduled At:', new Date(job.scheduled_at));
console.log('Recurring:', job.recurring);

List Jobs

// List all jobs
const jobs = await ductape.jobs.list();

// List with filters
const scheduledJobs = await ductape.jobs.list({
status: 'scheduled',
limit: 100,
offset: 0
});

// List jobs by product
const productJobs = await ductape.jobs.list({
product: 'my-app',
env: 'prd'
});

// List recurring jobs only
const recurringJobs = await ductape.jobs.list({
recurring: true
});

List Options

interface IJobListOptions {
status?: 'scheduled' | 'queued' | 'running' | 'completed' | 'failed' | 'cancelled' | 'paused';
recurring?: boolean;
product?: string;
env?: string;
namespace?: string; // 'actions', 'features', 'notifications', etc.
limit?: number;
offset?: number;
from?: number | string; // Start date filter
to?: number | string; // End date filter
}

Job Information

Job Object Structure

interface IJob {
id: string;
status: JobStatus;
namespace: string; // Which dispatch created it
product: string;
env: string;

// Schedule info
scheduled_at: number;
started_at?: number;
completed_at?: number;

// Recurring info
recurring: boolean;
cron?: string;
every?: number;
next_run_at?: number;
execution_count: number;
limit?: number;
end_date?: number;

// Retry info
retries: number;
retry_count: number;
last_error?: string;

// Payload
input: Record<string, unknown>;
result?: Record<string, unknown>;

// Metadata
created_at: number;
updated_at: number;
}

Check Job Status

const job = await ductape.jobs.get('job_abc123');

if (job.status === 'completed') {
console.log('Job completed successfully');
console.log('Result:', job.result);
} else if (job.status === 'failed') {
console.log('Job failed after', job.retry_count, 'attempts');
console.log('Last error:', job.last_error);
} else if (job.status === 'running') {
console.log('Job is currently executing');
}

Cancelling Jobs

Cancel a Single Job

// Cancel a scheduled job
await ductape.jobs.cancel('job_abc123');

// Cancel returns the updated job
const cancelledJob = await ductape.jobs.cancel('job_abc123');
console.log('Status:', cancelledJob.status); // 'cancelled'

Cancel Multiple Jobs

// Cancel all scheduled jobs for a product
const cancelled = await ductape.jobs.cancelMany({
product: 'my-app',
status: 'scheduled'
});

console.log('Cancelled jobs:', cancelled.count);

Cancel with Reason

await ductape.jobs.cancel('job_abc123', {
reason: 'Campaign ended early'
});

Pausing and Resuming Jobs

Pause a Recurring Job

// Pause a recurring job - it won't run until resumed
await ductape.jobs.pause('job_abc123');

const job = await ductape.jobs.get('job_abc123');
console.log('Status:', job.status); // 'paused'

Resume a Paused Job

// Resume a paused job
await ductape.jobs.resume('job_abc123');

const job = await ductape.jobs.get('job_abc123');
console.log('Status:', job.status); // 'scheduled'
console.log('Next run:', new Date(job.next_run_at));

Pause All Jobs for a Product

// Pause all recurring jobs for maintenance
await ductape.jobs.pauseMany({
product: 'my-app',
recurring: true
});

// Resume after maintenance
await ductape.jobs.resumeMany({
product: 'my-app',
status: 'paused'
});

Rescheduling Jobs

Reschedule a Job

// Reschedule a job to a new time
await ductape.jobs.reschedule('job_abc123', {
start_at: Date.now() + 7200000 // 2 hours from now
});

Update Recurring Schedule

// Change the cron schedule for a recurring job
await ductape.jobs.reschedule('job_abc123', {
cron: '0 10 * * *', // Change to 10 AM
tz: 'America/New_York'
});

// Change interval for interval-based job
await ductape.jobs.reschedule('job_abc123', {
every: 7200000 // Change to every 2 hours
});

Monitoring Job Execution

Get Execution History

// Get execution history for a recurring job
const history = await ductape.jobs.getHistory('job_abc123', {
limit: 10
});

for (const execution of history.executions) {
console.log(`Run ${execution.number}:`);
console.log(' Started:', new Date(execution.started_at));
console.log(' Duration:', execution.duration_ms, 'ms');
console.log(' Status:', execution.status);
if (execution.error) {
console.log(' Error:', execution.error);
}
}

Execution History Response

interface IJobHistory {
job_id: string;
total_executions: number;
successful_executions: number;
failed_executions: number;
executions: IJobExecution[];
}

interface IJobExecution {
number: number;
started_at: number;
completed_at?: number;
duration_ms?: number;
status: 'completed' | 'failed';
error?: string;
result?: Record<string, unknown>;
}

Get Job Statistics

// Get statistics for all jobs in a product
const stats = await ductape.jobs.getStats({
product: 'my-app',
env: 'prd'
});

console.log('Total jobs:', stats.total);
console.log('Scheduled:', stats.scheduled);
console.log('Running:', stats.running);
console.log('Completed:', stats.completed);
console.log('Failed:', stats.failed);
console.log('Success rate:', stats.success_rate);

Retrying Failed Jobs

Retry a Failed Job

// Retry a failed job immediately
await ductape.jobs.retry('job_abc123');

// Retry with delay
await ductape.jobs.retry('job_abc123', {
delay: 60000 // Wait 1 minute before retrying
});

Retry Multiple Failed Jobs

// Retry all failed jobs from today
await ductape.jobs.retryMany({
status: 'failed',
from: new Date().setHours(0, 0, 0, 0)
});

Deleting Jobs

Delete Completed Jobs

// Delete a specific job
await ductape.jobs.delete('job_abc123');

// Delete old completed jobs
await ductape.jobs.deleteMany({
status: 'completed',
to: Date.now() - 30 * 24 * 60 * 60 * 1000 // Older than 30 days
});

Job Events and Webhooks

You can configure webhooks to receive notifications about job events:

// Configure job completion webhook
await ductape.jobs.setWebhook({
url: 'https://api.example.com/webhooks/jobs',
events: ['job.completed', 'job.failed'],
secret: 'webhook-secret-key'
});

Webhook Events

EventDescription
job.scheduledJob was scheduled
job.startedJob execution started
job.completedJob completed successfully
job.failedJob failed (after retries)
job.cancelledJob was cancelled
job.pausedRecurring job was paused
job.resumedPaused job was resumed

Webhook Payload

interface IJobWebhookPayload {
event: string;
timestamp: number;
job: {
id: string;
status: string;
namespace: string;
product: string;
env: string;
scheduled_at: number;
execution_count?: number;
error?: string;
};
}

Best Practices

1. Use Meaningful Job IDs

When retrieving the job ID from dispatch, store it with context:

const job = await ductape.actions.dispatch({ ... });

// Store job reference with context
await saveJobReference({
job_id: job.job_id,
purpose: 'welcome_email',
user_id: userId,
created_at: Date.now()
});

2. Monitor Critical Jobs

Set up alerts for critical job failures:

const job = await ductape.jobs.get('job_abc123');

if (job.status === 'failed') {
await alertOps({
message: `Critical job ${job.id} failed: ${job.last_error}`,
severity: 'high'
});
}

3. Clean Up Old Jobs

Regularly clean up completed jobs to manage storage:

// Run monthly cleanup
await ductape.jobs.deleteMany({
status: ['completed', 'cancelled', 'failed'],
to: Date.now() - 90 * 24 * 60 * 60 * 1000 // Older than 90 days
});

4. Use Pausing for Maintenance

Pause jobs during system maintenance:

// Before maintenance
await ductape.jobs.pauseMany({ product: 'my-app' });

// ... perform maintenance ...

// After maintenance
await ductape.jobs.resumeMany({
product: 'my-app',
status: 'paused'
});

See Also