Skip to content

Upstash Workflow Engine

Relevant source files - .env.example - config/upstash.js - controllers/subscription.controller.js - controllers/workflow.controller.js - utils/email-template.js

The Upstash Workflow Engine provides the asynchronous infrastructure required to manage long-running subscription lifecycles. It leverages Upstash QStash to handle durable execution, allowing the system to schedule reminders days or weeks in advance without maintaining active server processes.

Workflow Configuration

The integration is centered around the workflowClient, which is configured using Upstash QStash credentials.

Workflow Client Setup

The client is initialized in config/upstash.js using the QSTASH_URL and QSTASH_TOKEN environment variables <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/config/upstash.js#L1-L7" min=1 max=7 file-path="config/upstash.js">Hii</FileRef>. This client is responsible for triggering new workflow instances whenever a subscription is created.

Triggering a Workflow

When a new subscription is successfully saved to MongoDB, the createSubscription controller initiates a workflow run <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/subscription.controller.js#L22-L31" min=22 max=31 file-path="controllers/subscription.controller.js">Hii</FileRef>.

Key parameters for the trigger include:

  • Target URL: The endpoint where the workflow logic resides (/api/v1/workflows/subscription/reminder) <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/subscription.controller.js#L23-L23" min=23 file-path="controllers/subscription.controller.js">Hii</FileRef>.
  • Payload: The subscriptionId is passed in the request body to allow the workflow to fetch relevant data <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/subscription.controller.js#L25-L25" min=25 file-path="controllers/subscription.controller.js">Hii</FileRef>.
  • Retries: Set to 0 to prevent duplicate workflow triggers in case of initial delivery failure, relying on the workflow engine's internal state management <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/subscription.controller.js#L30-L30" min=30 file-path="controllers/subscription.controller.js">Hii</FileRef>.

Workflow Execution Logic

The workflow logic is defined in controllers/workflow.controller.js using the @upstash/workflow/express``serve() handler <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L3-L10" min=3 max=10 file-path="controllers/workflow.controller.js">Hii</FileRef>.

The Reminder Schedule

The system uses a static array REMINDERS to define the notification intervals: [7, 5, 2, 1] days before the renewal date <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L8-L8" min=8 file-path="controllers/workflow.controller.js">Hii</FileRef>.

Execution Steps

The sendReminders function executes the following sequence:

  1. Fetch Data: Uses context.run to perform a durable database lookup via fetchSubscription``<FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L13-L13" min=13 file-path="controllers/workflow.controller.js">Hii</FileRef>. This step populates user details (name, email) required for the notification <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L35-L39" min=35 max=39 file-path="controllers/workflow.controller.js">Hii</FileRef>.
  2. Validation: Checks if the subscription is still active. If the subscription was canceled or deleted, the workflow terminates early <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L15-L15" min=15 file-path="controllers/workflow.controller.js">Hii</FileRef>.
  3. Iterative Sleeping: For each value in the REMINDERS array, the workflow calculates the reminderDate``<FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L24-L25" min=24 max=25 file-path="controllers/workflow.controller.js">Hii</FileRef>.
  4. Stateful Pausing: If the reminderDate is in the future, the workflow calls context.sleepUntil(), which offloads the execution state to Upstash until the specified timestamp <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L41-L45" min=41 max=45 file-path="controllers/workflow.controller.js">Hii</FileRef>.
  5. Notification Dispatch: Once the workflow resumes on the target date, it executes triggerReminder, which calls the sendReminderEmail utility <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L47-L57" min=47 max=57 file-path="controllers/workflow.controller.js">Hii</FileRef>.

Data Flow: Subscription Creation to Workflow Trigger

This diagram illustrates how the createSubscription controller interacts with the workflowClient and the external Upstash service.

sequenceDiagram
    participant Client
    participant SC as "controllers/subscription.controller.js"
    participant M as "models/subscription.model.js"
    participant UC as "config/upstash.js (workflowClient)"
    participant QStash as "Upstash QStash Service"
    Client->>SC: POST /api/v1/subscriptions
    SC->>M: Subscription.create(req.body)
    M-->>SC: subscription (with _id)
    SC->>UC: workflowClient.trigger({ url, body: { subscriptionId }, retries: 0 })
    UC->>QStash: HTTP POST (Trigger Workflow)
    QStash-->>UC: { workflowRunId }
    UC-->>SC: workflowRunId
    SC-->>Client: 201 Created (subscription, workflowRunId)

Sources:<FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/subscription.controller.js#L5-L43" min=5 max=43 file-path="controllers/subscription.controller.js">Hii</FileRef>, <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/config/upstash.js#L4-L7" min=4 max=7 file-path="config/upstash.js">Hii</FileRef>

The Workflow Handler (serve)

The serve() handler acts as the entry point for the Upstash QStash callbacks. It manages the execution state and ensures that steps wrapped in context.run are idempotent.

Implementation Detail: Date-Based Pausing

The workflow does not "block" a thread. Instead, it uses context.sleepUntil to instruct QStash to wake the application up at a specific Date object <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L44-L44" min=44 file-path="controllers/workflow.controller.js">Hii</FileRef>.

Code Entity Mapping: Workflow Lifecycle

This diagram maps the logical reminder steps to the specific functions and context methods used in the workflow.controller.js.

[Flowchart Diagram]

Sources:<FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L8-L57" min=8 max=57 file-path="controllers/workflow.controller.js">Hii</FileRef>

Key Functions and Classes

Entity Location Role
workflowClient <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/config/upstash.js#L4-L4" min=4 file-path="config/upstash.js">Hii</FileRef> Instance of @upstash/workflow``Client used to trigger runs.
serve() <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L10-L10" min=10 file-path="controllers/workflow.controller.js">Hii</FileRef> Middleware from @upstash/workflow/express that handles workflow orchestration.
fetchSubscription <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L35-L35" min=35 file-path="controllers/workflow.controller.js">Hii</FileRef> Helper that uses context.run to fetch a subscription with populated user data.
sleepUntilReminder <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L41-L41" min=41 file-path="controllers/workflow.controller.js">Hii</FileRef> Wrapper for context.sleepUntil to pause execution until a specific reminder date.
triggerReminder <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L47-L47" min=47 file-path="controllers/workflow.controller.js">Hii</FileRef> Executes the email dispatch step within a context.run block for reliability.

Sources:<FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/config/upstash.js#L1-L7" min=1 max=7 file-path="config/upstash.js">Hii</FileRef>, <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/workflow.controller.js#L1-L57" min=1 max=57 file-path="controllers/workflow.controller.js">Hii</FileRef>, <FileRef file-url="https://github.com/HetulMistry/subscription-tracker/blob/f319bd12/controllers/subscription.controller.js#L22-L31" min=22 max=31 file-path="controllers/subscription.controller.js">Hii</FileRef>