Claude Skills Guide

Building Fan-Out Parallel Tasks Workflows with Claude Code and Inngest

When building modern applications, you’ll often encounter scenarios where a single trigger needs to spawn multiple independent tasks that can execute concurrently. This pattern—called “fan-out”—is essential for processing bulk operations, sending notifications, generating reports, or handling webhooks at scale. Inngest, combined with Claude Code’s powerful development capabilities, provides an elegant solution for implementing these workflows.

Understanding the Fan-Out Pattern

The fan-out pattern allows you to take one event and distribute work across multiple parallel functions. Instead of processing items sequentially (which can be slow), fan-out enables you to handle dozens or hundreds of tasks simultaneously, dramatically improving performance.

For example, imagine you have a newsletter system where adding a subscriber triggers welcome emails, profile creation, analytics tracking, and preference setup. With a naive approach, these operations run one after another. With fan-out, they all execute in parallel, reducing response time from seconds to milliseconds.

Setting Up Inngest with Claude Code

Claude Code provides excellent tooling for working with Inngest. Using the appropriate skill, you can scaffold a complete Inngest project in minutes. Here’s how to get started:

First, create a new Inngest-enabled project:

// inngest/client.ts
import { Inngest } from "inngest";
import { type AsyncReturnType } from "inngest/types";

export const inngest = new Inngest({
  id: "my-newsletter-app",
  eventKey: process.env.INNGEST_EVENT_KEY,
});

// Type definitions for our events
export type Events = {
  "subscriber/created": {
    data: {
      email: string;
      name: string;
      preferences: string[];
    };
  };
};

Building the Fan-Out Workflow

Now let’s create the parallel task handlers. The key is using Inngest’s ability to spawn multiple function calls from a single event:

// inngest/functions/subscriber-welcome.ts
import { inngest } from "../client";

export const processSubscriberWelcome = inngest.createFunction(
  {
    id: "subscriber-welcome",
    name: "Process Subscriber Welcome",
  },
  { event: "subscriber/created" },
  async ({ event, step }) => {
    const { email, name, preferences } = event.data;

    // Fan-out: Run these tasks in parallel using Promise.all
    const [welcomeEmail, profileCreation, analyticsTrack, preferencesSetup] =
      await Promise.all([
        // Task 1: Send welcome email
        step.run("send-welcome-email", async () => {
          await sendEmail({
            to: email,
            subject: `Welcome, ${name}!`,
            template: "welcome",
          });
          return { status: "sent", type: "welcome" };
        }),

        // Task 2: Create user profile
        step.run("create-profile", async () => {
          return await db.profiles.create({
            email,
            name,
            createdAt: new Date(),
          });
        }),

        // Task 3: Track analytics event
        step.run("track-analytics", async () => {
          await analytics.track("subscriber.created", {
            email,
            timestamp: Date.now(),
          });
          return { tracked: true };
        }),

        // Task 4: Set up preferences
        step.run("setup-preferences", async () => {
          for (const pref of preferences) {
            await db.preferences.upsert({
              userId: email,
              preference: pref,
              enabled: true,
            });
          }
          return { preferences: preferences.length };
        }),
      ]);

    return {
      success: true,
      results: {
        welcomeEmail,
        profileCreation,
        analyticsTrack,
        preferencesSetup,
      },
    };
  }
);

Advanced: Dynamic Fan-Out with Steps

For more complex scenarios where the number of parallel tasks isn’t known beforehand, you can use Inngest’s step functions to dynamically create parallel work:

// inngest/functions/bulk-report-generation.ts
export const generateBulkReports = inngest.createFunction(
  {
    id: "bulk-report-generation",
    name: "Generate Bulk Reports",
  },
  { event: "reports/generate-bulk" },
  async ({ event, step }) => {
    const { reportIds, format, userId } = event.data;

    // Dynamically create parallel report generation tasks
    const reportPromises = reportIds.map((reportId) =>
      step.run(`generate-report-${reportId}`, async () => {
        const report = await generateReportData(reportId);
        const file = await renderReport(report, format);
        await saveReport(userId, reportId, file);
        return { reportId, status: "completed" };
      })
    );

    const results = await Promise.all(reportPromises);

    // Send summary notification
    await step.run("send-completion-notification", async () => {
      await notifyUser(userId, {
        message: `Generated ${results.length} reports`,
        format,
      });
    });

    return { generated: results.length, reports: results };
  }
);

Error Handling and Retries

Inngest provides automatic retry logic for failed steps. Configure retry policies based on your needs:

export const processWithCustomRetry = inngest.createFunction(
  {
    id: "process-with-retry",
    name: "Process with Custom Retry",
    retry: {
      attempts: 3,
      delay: "exponential",
      minTimeout: 1000,
      maxTimeout: 30000,
    },
  },
  { event: "data/process" },
  async ({ event, step }) => {
    // Your processing logic here
  }
);

Monitoring and Observability

Claude Code skills can help you set up comprehensive monitoring. Track the execution of your parallel workflows:

// Add structured logging for debugging
export const monitoredFunction = inngest.createFunction(
  {
    id: "monitored-parallel-tasks",
    name: "Monitored Parallel Tasks",
  },
  { event: "tasks/execute" },
  async ({ event, step, logger }) => {
    logger.info("Starting parallel task execution", {
      taskCount: event.data.tasks.length,
      correlationId: event.data.correlationId,
    });

    const startTime = Date.now();
    const results = await Promise.all(
      event.data.tasks.map((task) =>
        step.run(task.id, () => executeTask(task))
      )
    );

    const duration = Date.now() - startTime;
    logger.info("Parallel tasks completed", {
      duration,
      successCount: results.filter((r) => r.status === "success").length,
    });

    return { duration, results };
  }
);

Best Practices

When implementing fan-out workflows with Claude Code and Inngest, keep these tips in mind:

  1. Keep tasks independent: Ensure parallel tasks don’t depend on each other’s output
  2. Set appropriate timeouts: Use step-level timeouts for long-running operations
  3. Implement idempotency: Make your functions idempotent to handle retries safely
  4. Use batched events: For very high-volume scenarios, consider batching events
  5. Monitor execution times: Track parallel execution performance to optimize

Conclusion

Fan-out parallel task workflows are a powerful pattern for building scalable applications. With Claude Code’s development capabilities and Inngest’s event-driven architecture, you can implement robust parallel processing in minutes rather than hours. The combination of TypeScript safety, step functions, and automatic retries makes this approach production-ready from the start.

Start experimenting with these patterns in your next project, and you’ll quickly see how parallel task execution can transform your application’s performance and user experience.

Built by theluckystrike — More at zovo.one