~/docs / mercob

Mercob

The scheduled-job engine for the Mercy platform. Mercob polls the database for due jobs, dispatches them onto the same BullMQ queue used by Mercio, and tracks every execution attempt from dispatch to completion.


Overview

Mercob sits on top of Mercio. It does not run function code itself — it schedules HTTP calls to a deployed Mercio function and records the results. Every scheduled job:

  • Targets a deployed Mercio function by its ID.
  • Supports five schedule kinds: CRON, DAILY, WEEKLY, INTERVAL, and ONCE.
  • Configures the exact HTTP request to send (method, path, query, headers, body).
  • Retries on failure up to a configured limit, with per-attempt status tracking.
  • Enforces a per-invocation timeout. Timed-out jobs are marked TIMEOUT, not lost.
  • Records a full JobRun for every execution attempt, with logs streamed from mercio-runtime.

How it works

  1. 01

    Poller tick (every 60 s)

    The mercob service queries ScheduledJob rows where active = true and nextRunAt ≤ now + 60 s. The 60-second look-ahead window prevents jobs from being skipped due to clock skew. Due jobs are dispatched in parallel, up to 10 at a time.

  2. 02

    Dispatch

    For each due job, mercob creates a JobRun row (status = QUEUED) and calls the dispatch loop. Dispatch pushes a BullMQ job onto the mercio-invocations queue with the configured HTTP request parameters and timeoutMs.

  3. 03

    Execution via mercio-runtime

    mercio-runtime picks up the job, routes it to the warm workerd process for the target function ID, and makes the HTTP request. It enforces timeoutMs via AbortController. On completion (success or timeout), the BullMQ job result or failure is recorded.

  4. 04

    Result & retry

    Dispatch reads the BullMQ result. On success, the JobRun is patched to SUCCEEDED with HTTP status, response body, and duration. On failure, dispatch retries up to maxRetries times. After all retries are exhausted, the run is marked FAILED or TIMEOUT.

  5. 05

    Advance schedule

    After the run completes (regardless of outcome), nextRunAt is advanced for recurring jobs. One-shot jobs (ONCE) are deactivated after firing.


Quickstart

Before creating a scheduled job you need a deployed Mercio function. See the Mercio quickstart.

Create a scheduled job via the dashboard

  1. 01Navigate to /dashboard/mercob and click "New job".
  2. 02Select the target Mercio function from the dropdown.
  3. 03Give the job a name and configure the schedule (see Schedule kinds below).
  4. 04Set the HTTP method, path, and optional query/headers/body.
  5. 05Configure maxRetries (default 0) and timeoutMs (default 30 000 ms).
  6. 06Save. The job is active immediately and will fire on its next scheduled time.

Create a job via the API

bash
TOKEN="<your-jwt>"
FUNCTION_ID="<mercio-function-id>"

curl -X POST https://api.yourdomain.com/api/mercob/jobs \
  -H "Authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{
    "functionId": "'$FUNCTION_ID'",
    "name": "daily-report",
    "scheduleKind": "DAILY",
    "timeOfDay": "09:00",
    "method": "GET",
    "path": "/",
    "maxRetries": 2,
    "timeoutMs": 15000
  }'

Manually trigger a job

bash
JOB_ID="<job-id>"

curl -X POST https://api.yourdomain.com/api/mercob/jobs/$JOB_ID/trigger \
  -H "Authorization: Bearer $TOKEN"

Triggering manually creates a JobRun and dispatches it immediately, regardless of the schedule.


Schedule kinds

Set scheduleKind to one of the following values.

KindRequired fieldsDescription
INTERVALintervalSecRuns every N seconds after the previous run completes.
DAILYtimeOfDay (HH:MM UTC)Runs once per day at the specified UTC time.
WEEKLYtimeOfDay, daysOfWeek (0=Sun)Runs on specified days of the week at the given UTC time.
CRONcronExprStandard 5-field cron expression (parsed with cron-parser). Times are UTC.
ONCErunAt (ISO datetime)Fires once at the given time, then deactivates itself.

Examples

json
// Every 5 minutes
{ "scheduleKind": "INTERVAL", "intervalSec": 300 }

// Every day at 8:30 AM UTC
{ "scheduleKind": "DAILY", "timeOfDay": "08:30" }

// Every Monday and Wednesday at noon UTC
{ "scheduleKind": "WEEKLY", "timeOfDay": "12:00", "daysOfWeek": [1, 3] }

// Standard cron — every hour on weekdays
{ "scheduleKind": "CRON", "cronExpr": "0 * * * 1-5" }

// One-shot at a specific time
{ "scheduleKind": "ONCE", "runAt": "2026-06-01T09:00:00.000Z" }

Retries & timeouts

FieldDefaultDescription
maxRetries0Number of retry attempts after the first failure. 0 = no retries, run fails immediately.
timeoutMs30000Per-attempt timeout in milliseconds. Enforced by AbortController inside mercio-runtime.

Mercob waits for timeoutMs + 30 000 ms before giving up on a BullMQ job. The extra 30-second buffer ensures that mercio-runtime has time to cleanly fail the job before the dispatcher times out — preventing duplicate runs.


Run history

Every dispatch creates a JobRun record. You can view run history in the dashboard at /dashboard/mercob/[id] or via the API.

Run statuses

StatusMeaning
QUEUEDJobRun created; BullMQ job not yet picked up.
RUNNINGmercio-runtime is executing the function.
SUCCEEDEDFunction returned a response within timeoutMs.
FAILEDAll retry attempts exhausted with non-successful outcomes.
TIMEOUTFunction did not respond within timeoutMs on the final attempt.

Fetch run history via the API

bash
JOB_ID="<job-id>"

# paginated run list
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://api.yourdomain.com/api/mercob/jobs/$JOB_ID/runs?page=1&limit=20"

# single run with logs
RUN_ID="<run-id>"
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://api.yourdomain.com/api/mercob/runs/$RUN_ID"

Configuration

Set these in apps/mercob/.env.

VariableRequiredDescription
DATABASE_URLyesPostgreSQL connection string
REDIS_HOSTyesRedis host
REDIS_PORTnoRedis port (default 6379)
WORKER_SECRETyesShared secret for internal API calls (PATCH /internal/job-runs, POST /internal/job-logs)
API_BASE_URLnoBase URL of the API service (default http://localhost:3001)