Prerequisites

  • Node.js 18+
  • Redis 7.4+ (Railway or self-hosted; must be noeviction policy)
  • A Rotor workspace key (rt_ws_...)

Install

pnpm add @rotorsh/sdk bullmq ioredis
Warning

Rotor pins a specific BullMQ major version. Check your package.json after install — if bullmq is a different major than @rotorsh/sdk expects, you'll get a startup warning. Pin to the version listed in @rotorsh/sdk's peer dependencies.

The 5 Resources

1. Queues

Create and inspect queues:

import { Rotor } from "@rotorsh/sdk";
 
const rotor = new Rotor({ apiKey: process.env.ROTOR_API_KEY! });
 
// Create a queue
const queue = await rotor.queues.create({
  name: "outreach",
  retry_attempts: 3,
});
 
// List queues
const queues = await rotor.queues.list();
console.log(queues.data.map((q) => q.name));
 
// Get queue stats
const stats = await rotor.queues.stats("outreach");
console.log(stats.waiting, stats.active, stats.completed, stats.failed);

2. Jobs

Enqueue individual or batch jobs:

// Single job
const job = await rotor.jobs.enqueue("outreach", {
  payload: { leadId: "lead_123" },
});
 
// Batch (quota checked atomically — all-or-nothing)
const jobs = await rotor.jobs.addBatch("outreach", [
  { payload: { leadId: "lead_001" } },
  { payload: { leadId: "lead_002" } },
  { payload: { leadId: "lead_003" } },
]);
 
// Get job status
const status = await rotor.jobs.get("outreach", job.id);
console.log(status.state); // waiting | active | completed | failed | delayed

3. Schedules

Recurring jobs with cron syntax:

// Create a recurring enrichment job
const schedule = await rotor.schedules.create({
  queue: "enrichment",
  name: "nightly-enrichment",
  cron: "0 2 * * *", // 2am UTC daily
  payload: { source: "clearbit" },
  timezone: "UTC",
});
 
// Pause / resume via update
await rotor.schedules.update(schedule.id, { enabled: false });
await rotor.schedules.update(schedule.id, { enabled: true });

4. Status

Check workspace health and quota usage in a single call:

const status = await rotor.status.get();
// {
//   redis: 'ok',
//   api: 'ok',
//   version: '1.0.0',
//   quota: { used: 12450, limit: 100000, plan: 'pro', resetAt: '2025-05-01T00:00:00Z' }
// }

5. Usage

Billing-period usage breakdown:

const usage = await rotor.usage.current();
// { period: { from, to }, executions: { total, byQueue: {...} } }

Idempotent Handler Pattern

Prevent duplicate outreach when jobs retry:

import { RotorWorker } from "@rotorsh/sdk";
 
const worker = new RotorWorker({
  workspaceId: process.env.WORKSPACE_ID!,
  queueName: "outreach",
  connection: process.env.REDIS_URL!,
  concurrency: 5,
  processor: async (job) => {
    const { leadId } = job.data.payload;
 
    // Use job.id as idempotency key for downstream APIs
    const alreadySent = await db.outreach.findUnique({
      where: { rotorJobId: job.id },
    });
 
    if (alreadySent) {
      return { skipped: true, reason: "already-sent" };
    }
 
    await sendEmail(leadId);
 
    await db.outreach.create({
      data: { rotorJobId: job.id, leadId, sentAt: new Date() },
    });
 
    return { sent: true };
  },
});
 
await worker.ready();

SIGTERM Drain

Graceful shutdown — in-flight jobs complete before the process exits:

const worker = new RotorWorker({
  workspaceId: process.env.WORKSPACE_ID!,
  queueName: "outreach",
  connection: process.env.REDIS_URL!,
  concurrency: 5,
  drainTimeout: 30_000, // wait up to 30s for in-flight jobs
  processor: handler,
});
 
// RotorWorker registers SIGTERM/SIGINT handlers automatically.
// On Railway/Fly.io/Heroku: deploy sends SIGTERM → worker finishes
// current jobs → process exits cleanly. No jobs are lost.
await worker.ready();

Railway Deploy Notes

  1. Set ROTOR_API_KEY in Railway environment variables
  2. Set REDIS_URL to your Railway Redis private URL (use redis://...)
  3. Ensure Redis Memory Policy is noeviction:
    railway run redis-cli CONFIG SET maxmemory-policy noeviction
    
  4. Worker Procfile or start command: node dist/worker.js
  5. Deploy: Railway will send SIGTERM on deploys — SIGTERM drain ensures zero job loss

Next Steps