TypeScript SDK for dispatching typed background workflow events to a Cloudflare Worker runtime.
The package is transport-oriented and framework-agnostic. It provides a client, an HTTP transport, strongly typed event contracts, retry utilities, and job facades for email, notification, and payment workflows.
Related worker runtime: https://github.com/aashahin/cloudflare-workflows-worker
@abshahin/workflows-sdk separates event production from workflow execution.
- Producers only need to know how to send typed events
- The transport layer decides how those events reach the workflow runtime
- Event names and payloads stay typed across package boundaries
- Existing application code can migrate away from Inngest-style producers with minimal surface change
- The default companion runtime for this SDK is the Workflows Worker:
https://github.com/aashahin/cloudflare-workflows-worker
- Typed contracts for email, notification, and payment events
- HTTP transport for dispatching event batches to a worker endpoint
- Producer-friendly job facades:
EmailJobs,NotificationJobs,PaymentJobs - Transport retry support with exponential backoff
- Optional dual-run mode for phased migrations
- Optional
onSendExhaustedhook for persisting failed transport attempts - Sub-path exports for contracts and helpers
Inside this monorepo:
"@abshahin/workflows-sdk": "workspace:*"Once published publicly, install it with your package manager of choice:
bun add @abshahin/workflows-sdknpm install @abshahin/workflows-sdkimport {
EmailJobs,
HttpTransport,
NotificationJobs,
PaymentJobs,
createWorkflowsClient,
} from "@abshahin/workflows-sdk";
const client = createWorkflowsClient({
transport: new HttpTransport({
baseUrl: "https://workflows.example.com",
authToken: process.env.WORKFLOWS_AUTH_TOKEN!,
}),
});
const emailJobs = new EmailJobs(client, () => true);
const notificationJobs = new NotificationJobs(client);
const paymentJobs = new PaymentJobs(client);
await emailJobs.sendResetPasswordEmail({
email: "user@example.com",
userName: "John",
otpCode: "123456",
tenantId: "tenant_123",
});
await notificationJobs.addNotification(
"tenant_123",
"user_456",
{
title: "New course available",
message: "Check the dashboard for details.",
type: "info",
},
{ delay: 5_000 },
);
await paymentJobs.processPayout({
tenantId: "tenant_123",
transactionId: "txn_001",
walletId: "wallet_001",
amount: 250,
currency: "USD",
});flowchart LR
A[Application code] --> B[Job facade]
B --> C[WorkflowsClient]
C --> D[Transport]
D --> E[Worker /dispatch endpoint]
E --> F[Workflow runtime]
Creates a WorkflowsClient from a transport configuration.
Use it when you want a minimal factory instead of constructing the client class directly.
Sends one or more workflow events to the worker's /dispatch endpoint.
Config:
baseUrl: worker base URLauthToken: shared bearer tokentimeoutMs: request timeout, default10000retry: retry policy, orfalseto disable transport retries
Transport behavior:
- Trims trailing slashes from
baseUrl - Wraps network/timeouts as
WorkflowSendError - Treats
429and all5xxresponses as retryable - Treats other
4xxresponses as non-retryable transport failures - Logs partial batch failures without throwing away successful IDs
Facade for email-related workflow events.
Notable methods:
sendResetPasswordEmailsendVerificationEmailsendChangeEmailVerificationsendNewAccountCredentialssendInvitationEmailsendEnrollmentConfirmationEmailsendCartRecoveryEmailsendTrialEndingRemindersendPaymentReceiptEmailsendWithdrawalStatusEmailsendFailedPaymentAlertEmailsendRefundConfirmationEmail
The constructor accepts an optional isEnabled callback to short-circuit email dispatch when email delivery is disabled.
Facade for notification-related workflow events.
Notable methods:
addNotificationaddNotificationForCustomeraddBulkNotificationaddMultipleNotifications
addMultipleNotifications preserves per-item delays by sending individual events only when needed; otherwise it batches them.
Facade for payment-related workflow events.
Current method:
processPayout
This dispatches the payout orchestration workflow handled by the worker runtime.
import {
EmailJobs,
HttpTransport,
createWorkflowsClient,
} from "@abshahin/workflows-sdk";
const client = createWorkflowsClient({
transport: new HttpTransport({
baseUrl: "https://workflows.example.com",
authToken: "secret",
}),
});
const emailJobs = new EmailJobs(client, () => true);
await emailJobs.sendVerificationEmail(
{
email: "user@example.com",
otpCode: "123456",
tenantId: "tenant_123",
},
{ delay: 5_000 },
);import {
HttpTransport,
NotificationJobs,
createWorkflowsClient,
} from "@abshahin/workflows-sdk";
const client = createWorkflowsClient({
transport: new HttpTransport({
baseUrl: "https://workflows.example.com",
authToken: "secret",
}),
});
const notificationJobs = new NotificationJobs(client);
await notificationJobs.addBulkNotification(
"tenant_123",
{
title: "Billing update",
message: "Your subscription has been renewed.",
type: "billing",
},
["user_1", "user_2"],
);import {
HttpTransport,
PaymentJobs,
createWorkflowsClient,
} from "@abshahin/workflows-sdk";
const client = createWorkflowsClient({
transport: new HttpTransport({
baseUrl: "https://workflows.example.com",
authToken: "secret",
}),
});
const paymentJobs = new PaymentJobs(client);
await paymentJobs.processPayout({
tenantId: "tenant_123",
transactionId: "txn_001",
walletId: "wallet_001",
amount: 250,
currency: "USD",
});Use dual-run mode when migrating between workflow backends or validating a new transport.
import { HttpTransport, createWorkflowsClient } from "@abshahin/workflows-sdk";
const primaryTransport = new HttpTransport({
baseUrl: "https://primary.example.com",
authToken: "primary-secret",
});
const shadowTransport = new HttpTransport({
baseUrl: "https://shadow.example.com",
authToken: "shadow-secret",
});
const client = createWorkflowsClient({
transport: primaryTransport,
shadowTransport,
dualRun: true,
});The primary transport result is returned. Shadow transport failures are logged and suppressed.
Use onSendExhausted when you want to store failed dispatches for later replay instead of letting producer-side business logic fail immediately.
import { HttpTransport, createWorkflowsClient } from "@abshahin/workflows-sdk";
const client = createWorkflowsClient({
transport: new HttpTransport({
baseUrl: "https://workflows.example.com",
authToken: "secret",
}),
onSendExhausted: async ({ events, options, error, attempts }) => {
console.error("Persist failed workflow dispatch", {
attempts,
error: error.message,
eventCount: events.length,
traceId: options?.traceId,
});
},
});When persistence succeeds, the client returns { ids: [] } instead of throwing.
email/reset-passwordemail/new-account-credentialsemail/change-email-verificationemail/verificationemail/cart-recoveryemail/invitationemail/enrollment-confirmationemail/trial-reminderemail/payment-receiptemail/withdrawal-statusemail/failed-payment-alertemail/refund-confirmation
notification/createnotification/create-for-customernotification/bulk-create
payment/process-payout
| Export | Description |
|---|---|
createWorkflowsClient |
Factory for WorkflowsClient |
WorkflowsClient |
Core dispatch client |
HttpTransport |
HTTP transport adapter |
EmailJobs |
Email job facade |
NotificationJobs |
Notification job facade |
PaymentJobs |
Payment job facade |
EMAIL_EVENTS / NOTIFICATION_EVENTS / PAYMENT_EVENTS |
Event name constants |
withRetry / getBackoffDelay / DEFAULT_RETRY_POLICY |
Retry helpers |
deriveIdempotencyKey / generateEventId |
Idempotency helpers |
WorkflowError / WorkflowSendError / WorkflowValidationError / WorkflowRetryExhaustedError |
Typed errors |
The SDK exposes sub-path imports for tree-shaking or isolated usage:
import {
EMAIL_EVENTS,
type ResetPasswordEmailData,
} from "@abshahin/workflows-sdk/contracts";
import {
deriveIdempotencyKey,
withRetry,
} from "@abshahin/workflows-sdk/helpers";The job facades are designed to make migration straightforward, but there is one important API difference.
Previous Inngest usage relied on static methods:
- EmailJobs.sendResetPasswordEmail(data);
+ emailJobs.sendResetPasswordEmail(data);The SDK uses instance-based facades so transports, feature flags, and fallback behavior can be injected once at application bootstrap.
MIT. Add the LICENSE file in the published workflows-sdk repository.