Sending events from functions

In some workflows or pipeline functions, you may want to broadcast events from within your function to trigger other functions. This pattern is useful when:

  • You want to decouple logic into separate functions that can be re-used across your system
  • You want to send an event to fan-out to multiple other functions
  • Your function is handling many items that you want to process in parallel functions
  • You want to cancel another function
  • You want to send data to another function waiting for an event

If your function needs to handle the result of another function, or wait until that other function has completed, you should use direct function invocation instead.

How to send events from functions

To send events from within functions, you will use step.sendEvent(). This method takes a single event, or an array of events. The example below uses an array of events.

This is an example of a scheduled function that sends a weekly activity email to all users.

First, the function fetches all users, then it maps over all users to create a "app/weekly-email-activity.send" event for each user, and finally it sends all events to Inngest.

import { GetEvents, Inngest } from "inngest";

const inngest = new Inngest({ id: "signup-flow" });
type Events = GetEvents<typeof inngest>;

export const loadCron = inngest.createFunction(
  { id: "weekly-activity-load-users" },
  { cron: "0 12 * * 5" },
  async ({ event, step }) => {
    // Fetch all users
    const users = await step.run("fetch-users", async () => {
      return fetchUsers();
    });

    // For each user, send us an event.  Inngest supports batches of events
    // as long as the entire payload is less than 512KB.
    const events = users.map<Events["app/weekly-email-activity.send"]>(
      (user) => {
        return {
          name: "app/weekly-email-activity.send",
          data: {
            ...user,
          },
          user,
        };
      }
    );

    // Send all events to Inngest, which triggers any functions listening to
    // the given event names.
    await step.sendEvent("fan-out-weekly-emails", events);

    // Return the number of users triggered.
    return { count: users.length };
  }
);

Next, create a function that listens for the "app/weekly-email-activity.send" event. This function will be triggered for each user that was sent an event in the previous function.

export const sendReminder = inngest.createFunction(
  { id: "weekly-activity-send-email" },
  { event: "app/weekly-email-activity.send" },
  async ({ event, step }) => {
    const data = await step.run("load-user-data", async () => {
      return loadUserData(event.data.user.id);
    });

    await step.run("email-user", async () => {
      return sendEmail(event.data.user, data);
    });
  }
);

Each of these functions will run in parallel and individually retry on error, resulting in a faster, more reliable system.

💡 Tip: When triggering lots of functions to run in parallel, you will likely want to configure concurrency limits to prevent overloading your system. See our concurrency guide for more information.

Why step.sendEvent() vs. inngest.send()?

By using step.sendEvent() Inngest's SDK can automatically add context and tracing which ties events to the current function run. If you use inngest.send(), the context around the function run is not present.

Parallel functions vs. parallel steps

Another technique similar is running multiple steps in parallel (read the step parallelism guide). Here are the key differences:

  • Both patterns run code in parallel
  • With parallel steps, you can access the output of each step, whereas with the above example, you cannot
  • Parallel steps have limit of 1,000 steps, though you can trigger as many functions as you'd like using the send event pattern
  • Decoupled functions can be tested and replayed separately, whereas parallel steps can only be tested as a whole
  • You can retry individual functions easily if they permanently fail, whereas if a step permanently fails (after retrying) the function itself will fail and terminate.

Sending events vs. invoking

A related pattern is invoking external functions directly instead of just triggering them with an event. See the Invoking functions directly guide. Here are some key differences:

  • Sending events from functions is better suited for parallel processing of independent tasks and invocation is better for coordinated, interdependent functions
  • Sending events can be done in bulk, whereas invoke can only invoke one function at a time.
  • Sending events can be combined with fan-out to trigger multiple functions from a single event
  • Unlike invocation, sending events will not receive the result of the invoked function