Fair Supply LogoFair Supply - Docs

Inngest Infrastructure

Inngest handles background jobs and event-driven workflows for the FSA Platform.

Key Files

TypeLocation
Clientpackages/core/src/infrastructure/inngest/client.ts
Eventspackages/core/src/domain/events.ts
Jobspackages/core/src/application/jobs/

Event Definition

// packages/core/src/domain/events.ts
export type InngestEvents = {
  'organisation.create.provisional': {
    data: { accountId: string; name: string; recordId?: number };
  };
  'transaction.create': {
    data: { accountId: string; name: string; organisationId: string; segmentId?: string };
  };
};

Job Function Template

// packages/core/src/application/jobs/organisation/create-provisional.ts
import { inngest } from '../../../infrastructure/inngest/client';

export const createProvisionalOrganisation = inngest.createFunction(
  {
    id: 'organisation-create-provisional',
    concurrency: { limit: 100 },
    throttle: { limit: 1000, period: '1m', burst: 100 },
  },
  { event: 'organisation.create.provisional' },
  async ({ event, step }) => {
    const { accountId, name, recordId } = event.data;

    // Step 1: Create organisation
    const org = await step.run('create-provisional', async () => {
      return new CreateProvisionalOrganisation().execute({ diffbotId: null, name });
    });

    // Step 2: Link to account
    if (accountId) {
      await step.run('link-to-account', async () => {
        await AccountRepository.addOrganisation(accountId, { organisationId: org.id, name: org.name, recordId });
      });
    }

    return { organisationId: org.id, name: org.name };
  }
);

Step Patterns

Sequential Steps

const result1 = await step.run('step-1', () => doFirst());
const result2 = await step.run('step-2', () => doSecond(result1));

Parallel Steps

const [a, b] = await Promise.all([
  step.run('parallel-1', () => doA()),
  step.run('parallel-2', () => doB()),
]);

Wait for Event

const childEvent = await step.waitForEvent('wait-for-child', {
  event: 'child.completed',
  match: 'data.parentId',
  timeout: '1h',
});

Send Events

await step.sendEvent('emit-event', {
  name: 'something.happened',
  data: { id: '123' },
});

Error Handling

Non-Fatal Errors

await step.run('optional-step', async () => {
  try {
    return await riskyOperation();
  } catch (error) {
    console.error('Non-fatal:', error);
    return null;  // Continue execution
  }
});

Failure Handler

inngest.createFunction(
  {
    id: 'my-function',
    retries: 3,
    onFailure: async ({ error, event }) => {
      await notifyFailure(error, event);
    },
  },
  { event: 'my.event' },
  async ({ event, step }) => { ... }
);

Concurrency & Throttling

{
  id: 'my-function',
  concurrency: {
    limit: 100,
    key: 'event.data.accountId',  // Per account
  },
  throttle: {
    limit: 1000,
    period: '1m',
    burst: 100,
  },
}

Triggering Jobs

import { inngest } from '@repo/core/infrastructure/inngest/client';

// From use case or server action
await inngest.send({
  name: 'organisation.create.provisional',
  data: { accountId, name },
});

Best Practices

  1. Use steps for durability — each step is checkpointed
  2. Idempotent operations — steps may be retried
  3. Meaningful step names — for debugging in the Inngest dashboard
  4. Fire-and-forget fan-out — use step.sendEvent to dispatch child work, avoid waitForEvent at scale
  5. Use step.invoke — to call child functions and get their return values (1 step, no event matching)
  6. Concurrency limits — prevent overwhelming resources
  7. Stay under 1000 steps — Inngest limit per function run; use fan-out to child functions instead
TypeLocation
Clientpackages/core/src/infrastructure/inngest/client.ts
Eventspackages/core/src/domain/events.ts
Jobspackages/core/src/application/jobs/
API Routeapps/web/src/app/api/inngest/route.ts

On this page