Prolifto
Developer guide

Background jobs

Run asynchronous work with Inngest for email, PDFs, imports, and notifications.

Prolifto uses Inngest for asynchronous work. The admin app serves the Inngest endpoint at /api/inngest and registers every function in apps/admin/inngest/functions.

Local development

pnpm dev:admin starts the Inngest dev server on port 8310 alongside the admin app. Open the Inngest dev UI to inspect runs, replay events, and debug steps.

Environment:

INNGEST_DEV="http://127.0.0.1:8310"

In production, set INNGEST_EVENT_KEY and INNGEST_SIGNING_KEY.

Run categories

CategoryFunctions
NotificationsOrder created, order status changed, customer created, low stock, out of stock
Email/SMS/WhatsAppOrder confirmation email, order SMS, resend SMS, WhatsApp order messages and status callbacks
DocumentsInvoice PDF, receipt PDF
CatalogProduct CSV import, product search indexing and removal
AIProduct description generation
BillingSubscription renewals and lifecycle
DomainsStorefront domain revalidation
WhatsAppToken health, template provisioning, inbound messages

Events

Functions are triggered by events such as:

order/created
order/status.changed
customer/created
invoice/pdf.generate
receipt/pdf.generate
product/import
product/search.index
product/search.remove
product/description.generate

Events carry the organization ID and the entity ID. Functions should re-check the entity in the database rather than trusting stale event data.

Writing a function

  1. Create a file in apps/admin/inngest/functions/<area>/.
  2. Define the function with inngest.createFunction.
  3. Use step.run for each unit of work so retries are safe.
  4. Register it in apps/admin/app/api/inngest/route.ts.
  5. Send the event from the service that owns the change.
  6. Add tests for the business logic where it matters.

Example shape:

export const exampleFn = inngest.createFunction(
  {
    id: "example",
    triggers: { event: "example/created" },
  },
  async ({ event, step }) => {
    const result = await step.run("load", () =>
      loadSomething(event.data.id as string)
    );

    await step.run("process", () => processSomething(result));
  }
);

Reliability

  • Steps should be idempotent; Inngest retries failed steps.
  • Do not perform side effects before the step that owns them.
  • Use separate steps for external calls (email, storage, PDFs) so a failure does not repeat database writes.
  • Keep payloads small; pass IDs and reload data inside the function.
  • Log with the shared logger and include the organization and entity IDs.

Debugging

  • Check the Inngest dev UI for the failed step and its output.
  • Verify the event was sent and the function is registered.
  • Check the relevant environment variables (SMTP, storage, Stripe, WhatsApp).
  • Use the system log or the entity's activity timeline to confirm the expected side effect.

On this page