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
subscriptionIdis 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
0to 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:
- Fetch Data: Uses
context.runto perform a durable database lookup viafetchSubscription``<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>. - 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>. - Iterative Sleeping: For each value in the
REMINDERSarray, the workflow calculates thereminderDate``<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>. - Stateful Pausing: If the
reminderDateis in the future, the workflow callscontext.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>. - Notification Dispatch: Once the workflow resumes on the target date, it executes
triggerReminder, which calls thesendReminderEmailutility<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>