Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rjdellecese/confect/llms.txt

Use this file to discover all available pages before exploring further.

The Scheduler service allows you to schedule mutations to run at a specific time or after a delay. This is essential for implementing delayed tasks, reminders, background jobs, and time-based workflows.

Overview

Schedule After Delay

Run a function after a specific duration

Schedule at Time

Run a function at an exact timestamp

Effect Integration

Use Effect’s Duration and DateTime types

Cancellable

Cancel scheduled functions before they run

Accessing the Scheduler

The Scheduler service is available in mutations and actions:
import { Scheduler } from "@confect/server";
import { Effect, Duration } from "effect";

const scheduleTask = Effect.gen(function* () {
  const scheduler = yield* Scheduler;
  
  // Schedule a function
  yield* scheduler.runAfter(
    Duration.hours(1),
    refs.internal.tasks.process,
    { taskId: "task-123" }
  );
});

Scheduling Methods

runAfter

Schedule a function to run after a duration:
import { Scheduler } from "@confect/server";
import { Effect, Duration } from "effect";
import { refs } from "../confect/_generated/refs";

const scheduleAfterDelay = Effect.gen(function* () {
  const scheduler = yield* Scheduler;
  
  // Schedule after 5 minutes
  yield* scheduler.runAfter(
    Duration.minutes(5),
    refs.internal.notifications.send,
    { userId: "user-123", message: "Reminder" }
  );
  
  // Schedule after 1 hour
  yield* scheduler.runAfter(
    Duration.hours(1),
    refs.internal.cleanup.removeOldData,
    {}
  );
  
  // Schedule after 30 seconds
  yield* scheduler.runAfter(
    Duration.seconds(30),
    refs.internal.processing.retry,
    { attemptId: "attempt-456" }
  );
});
Use Effect’s Duration API to create type-safe durations: Duration.seconds(), Duration.minutes(), Duration.hours(), Duration.days()

runAt

Schedule a function to run at a specific time:
import { Scheduler } from "@confect/server";
import { Effect, DateTime } from "effect";
import { refs } from "../confect/_generated/refs";

const scheduleAtTime = Effect.gen(function* () {
  const scheduler = yield* Scheduler;
  
  // Schedule at specific timestamp
  const targetTime = DateTime.unsafeMake(new Date("2026-03-08T12:00:00Z"));
  
  yield* scheduler.runAt(
    targetTime,
    refs.internal.reports.generate,
    { reportType: "monthly" }
  );
});
Use Effect’s DateTime API for type-safe date/time handling.

Common Use Cases

Email Verification Reminder

import { FunctionImpl, DatabaseWriter, Scheduler } from "@confect/server";
import { Effect, Duration } from "effect";

const registerUser = FunctionImpl.make(
  api,
  "users",
  "register",
  ({ email, password }) =>
    Effect.gen(function* () {
      const db = yield* DatabaseWriter<typeof databaseSchema>();
      const scheduler = yield* Scheduler;
      
      // Create user
      const userId = yield* db.table("users").insert({
        email,
        passwordHash: password, // hash this!
        verified: false,
        createdAt: Date.now(),
      });
      
      // Send verification email immediately
      // (implement this)
      
      // Schedule reminder after 24 hours
      yield* scheduler.runAfter(
        Duration.hours(24),
        refs.internal.users.sendVerificationReminder,
        { userId }
      );
      
      return { userId };
    }).pipe(Effect.orDie)
);

Trial Expiration

const startTrial = FunctionImpl.make(
  api,
  "subscriptions",
  "startTrial",
  ({ userId }) =>
    Effect.gen(function* () {
      const db = yield* DatabaseWriter<typeof databaseSchema>();
      const scheduler = yield* Scheduler;
      
      // Create subscription
      const subscriptionId = yield* db.table("subscriptions").insert({
        userId,
        status: "trial",
        startedAt: Date.now(),
      });
      
      // Schedule trial end after 14 days
      yield* scheduler.runAfter(
        Duration.days(14),
        refs.internal.subscriptions.endTrial,
        { subscriptionId }
      );
      
      return { subscriptionId };
    }).pipe(Effect.orDie)
);

Retry with Exponential Backoff

const retryWithBackoff = (
  attempt: number,
  maxAttempts: number,
  taskId: string
) =>
  Effect.gen(function* () {
    const scheduler = yield* Scheduler;
    
    if (attempt >= maxAttempts) {
      return yield* Effect.fail(new Error("Max retries reached"));
    }
    
    // Calculate exponential backoff: 2^attempt minutes
    const delayMinutes = Math.pow(2, attempt);
    
    yield* scheduler.runAfter(
      Duration.minutes(delayMinutes),
      refs.internal.tasks.retry,
      { taskId, attempt: attempt + 1 }
    );
  });

Session Cleanup

const createSession = FunctionImpl.make(
  api,
  "sessions",
  "create",
  ({ userId, expiresInHours = 24 }) =>
    Effect.gen(function* () {
      const db = yield* DatabaseWriter<typeof databaseSchema>();
      const scheduler = yield* Scheduler;
      
      const sessionId = yield* db.table("sessions").insert({
        userId,
        token: generateToken(),
        createdAt: Date.now(),
      });
      
      // Schedule cleanup when session expires
      yield* scheduler.runAfter(
        Duration.hours(expiresInHours),
        refs.internal.sessions.cleanup,
        { sessionId }
      );
      
      return { sessionId };
    }).pipe(Effect.orDie)
);

Scheduled Function Implementation

Implement the scheduled functions as internal mutations:
const sendVerificationReminder = FunctionImpl.make(
  api,
  "users",
  "sendVerificationReminder",
  ({ userId }) =>
    Effect.gen(function* () {
      const db = yield* DatabaseReader<typeof databaseSchema>();
      
      const user = yield* db.table("users").get(userId);
      
      // Only send if still not verified
      if (!user.verified) {
        // Send reminder email
        yield* sendEmail(user.email, "Please verify your email");
      }
    }).pipe(Effect.orDie)
);

const endTrial = FunctionImpl.make(
  api,
  "subscriptions",
  "endTrial",
  ({ subscriptionId }) =>
    Effect.gen(function* () {
      const db = yield* DatabaseWriter<typeof databaseSchema>();
      
      yield* db.table("subscriptions").patch(subscriptionId, {
        status: "expired",
        endedAt: Date.now(),
      });
    }).pipe(Effect.orDie)
);
Scheduled functions must be internal mutations or actions. They cannot be public functions.

Canceling Scheduled Functions

You can cancel scheduled functions before they run:
import { DatabaseWriter } from "@confect/server";
import { Effect } from "effect";

const verifyEmail = FunctionImpl.make(
  api,
  "users",
  "verify",
  ({ userId, token }) =>
    Effect.gen(function* () {
      const db = yield* DatabaseWriter<typeof databaseSchema>();
      
      // Verify the user
      yield* db.table("users").patch(userId, {
        verified: true,
      });
      
      // Find and cancel the reminder
      const scheduled = yield* db
        .table("_scheduled_functions")
        .filter((q) => 
          q.and(
            q.eq(q.field("name"), "internal/users/sendVerificationReminder"),
            q.eq(q.field("state.kind"), "pending")
          )
        )
        .first();
      
      if (scheduled) {
        yield* db.table("_scheduled_functions").delete(scheduled._id);
      }
    }).pipe(Effect.orDie)
);

Querying Scheduled Functions

View scheduled functions in the _scheduled_functions system table:
const listScheduledTasks = Effect.gen(function* () {
  const db = yield* DatabaseReader<typeof databaseSchema>();
  
  const scheduled = yield* db
    .table("_scheduled_functions")
    .filter((q) => q.eq(q.field("state.kind"), "pending"))
    .collect();
  
  return scheduled.map((task) => ({
    id: task._id,
    name: task.name,
    scheduledTime: task.scheduledTime,
    args: task.args,
  }));
});
The _scheduled_functions table tracks all scheduled functions with their state: pending, inProgress, success, failed, or canceled.

Scheduling from Actions

You can also schedule functions from actions:
import { Scheduler, MutationRunner } from "@confect/server";
import { Effect, Duration } from "effect";

const processWebhook = FunctionImpl.make(
  api,
  "webhooks",
  "process",
  ({ payload }) =>
    Effect.gen(function* () {
      const scheduler = yield* Scheduler;
      const runMutation = yield* MutationRunner;
      
      // Validate webhook
      const valid = validateWebhook(payload);
      
      if (!valid) {
        return yield* Effect.fail(new Error("Invalid webhook"));
      }
      
      // Store webhook data
      const webhookId = yield* runMutation(
        refs.internal.webhooks.store,
        { payload }
      );
      
      // Schedule processing
      yield* scheduler.runAfter(
        Duration.seconds(30),
        refs.internal.webhooks.processDelayed,
        { webhookId }
      );
    }).pipe(Effect.orDie)
);

Scheduled Function Arguments

Scheduled functions receive the arguments you pass:
// Scheduling
yield* scheduler.runAfter(
  Duration.hours(1),
  refs.internal.tasks.process,
  {
    taskId: "task-123",
    priority: "high",
    metadata: { source: "api" },
  }
);

// Implementation
const processTask = FunctionImpl.make(
  api,
  "tasks",
  "process",
  ({ taskId, priority, metadata }) =>
    Effect.gen(function* () {
      // taskId, priority, and metadata are available
      console.log(`Processing task ${taskId} with priority ${priority}`);
      
      // Process task...
    }).pipe(Effect.orDie)
);

Time Zone Considerations

import { DateTime } from "effect";

const scheduleDailyReport = Effect.gen(function* () {
  const scheduler = yield* Scheduler;
  
  // Schedule for 9 AM tomorrow in user's timezone
  const now = DateTime.now();
  const tomorrow = DateTime.add(now, Duration.days(1));
  const targetTime = DateTime.setHours(tomorrow, 9, 0, 0, 0);
  
  yield* scheduler.runAt(
    targetTime,
    refs.internal.reports.generateDaily,
    { userId: "user-123" }
  );
});
Effect’s DateTime API uses UTC by default. Convert to specific time zones using DateTime utilities.

Best Practices

1

Use Internal Functions

Always schedule internal mutations or actions, never public functions.
2

Handle Failures

Implement error handling in scheduled functions - they may fail and won’t automatically retry.
3

Store Schedule IDs

If you need to cancel scheduled functions, store their IDs in your database.
4

Idempotent Operations

Make scheduled functions idempotent in case they run multiple times.
Scheduled functions have a maximum execution time of 10 minutes (same as regular mutations). Break long-running tasks into smaller chunks.

Limitations

  • Scheduled functions must be mutations or actions (not queries)
  • Minimum scheduling delay is 1 second
  • Maximum scheduling delay is 5 years
  • Functions are scheduled in UTC time

Next Steps

Crons

Schedule recurring tasks

Functions

Learn about mutations and actions

Database

Store scheduling metadata

Node Actions

Schedule Node.js actions

Build docs developers (and LLMs) love