Wait for an Event

One step method is available to pause a Function's run until a given event is sent.

This is a useful pattern to react to specific user actions (for example, implement "Human in the loop" in AI Agent workflows).

Use step.waitForEvent() to wait for a particular event to be received before continuing. It returns a Promise that is resolved with the received event or null if the event is not received within the timeout.

export default inngest.createFunction(
  { id: "send-onboarding-nudge-email" },
  { event: "app/account.created" },
  async ({ event, step }) => {
    const onboardingCompleted = await step.waitForEvent(
      "wait-for-onboarding-completion",
      { event: "app/onboarding.completed", timeout: "3d", match: "data.userId" }
    );
    if (!onboardingCompleted) {
      // if no event is received within 3 days, onboardingCompleted will be null
    } else {
      // if the event is received, onboardingCompleted will be the event payload object
    }
  }
);

Check out the step.waitForEvent() TypeScript reference.

To add a simple time based delay to your code, use step.sleep() instead.

Examples

Dynamic functions that wait for additional user actions

Below is an example of an Inngest function that creates an Intercom or Customer.io-like drip email campaign, customized based on

export default inngest.createFunction(
  { id: "onboarding-email-drip-campaign" },
  { event: "app/account.created" },
  async ({ event, step }) => {
    // Send the user the welcome email immediately
    await step.run("send-welcome-email", async () => {
      await sendEmail(event.user.email, "welcome");
    });

    // Wait up to 3 days for the user to complete the final onboarding step
    // If the event is received within these 3 days, onboardingCompleted will be the
    // event payload itself, if not it will be null
    const onboardingCompleted = await step.waitForEvent("wait-for-onboarding", {
      event: "app/onboarding.completed",
      timeout: "3d",
      // The "data.userId" must match in both the "app/account.created" and
      // the "app/onboarding.completed" events
      match: "data.userId",
    });

    // If the user has not completed onboarding within 3 days, send them a nudge email
    if (!onboardingCompleted) {
      await step.run("send-onboarding-nudge-email", async () => {
        await sendEmail(event.user.email, "onboarding_nudge");
      });
    } else {
      // If they have completed onboarding, send them a tips email
      await step.run("send-tips-email", async () => {
        await sendEmail(event.user.email, "new_user_tips");
      });
    }
  }
);

Advanced event matching with if

For more complex functions, you may want to match the event payload against some other value. This could be a hard coded value like a billing plan name, a greater than filter for a number value or a value returned from a previous step.

In this example, we have built an AI blog post generator which returns three ideas to the user to select. Then when the user selects an idea from that batch of ideas, we generate an entire blog post and save it.

export default inngest.createFunction(
  { id: "generate-blog-post-with-ai" },
  { event: "ai/post.generator.requested" },
  async ({ event, step }) => {
    // Generate a number of suggestions for topics with OpenAI
    const generatedTopics = await step.run("generate-topic-ideas", async () => {
      const completion = await openai.createCompletion({
        model: "text-davinci-003",
        prompt: helpers.topicIdeaPromptWrapper(event.data.prompt),
        n: 3,
      });
      return {
        completionId: completion.data.id,
        topics: completion.data.choices,
      };
    });

    // Send the topics to the user via Websockets so they can select one
    // Also send the completion id so we can match that later
    await step.run("send-user-topics", () => {
      pusher.sendToUser(event.data.userId, "topics_generated", {
        sessionId: event.data.sessionId,
        completionId: generatedTopics.completionId,
        topics: generatedTopics.topics,
      });
    });

    // Wait up to 5 minutes for the user to select a topic
    // Ensuring the topic is from this batch of suggestions generated
    const topicSelected = await step.waitForEvent("wait-for-topic-selection", {
      event: "ai/post.topic.selected",
      timeout: "5m",
      // "async" is the "ai/post.topic.selected" event here:
      if: `async.data.completionId == "${generatedTopics.completionId}"`,
    });

    // If the user selected a topic within 5 minutes, "topicSelected" will
    // be the event payload, otherwise it is null
    if (topicSelected) {
      // Now that we've confirmed the user selected their topic idea from
      // this batch of suggestions, let's generate a blog post
      await step.run("generate-blog-post-draft", async () => {
        const completion = await openai.createCompletion({
          model: "text-davinci-003",
          prompt: helpers.blogPostPromptWrapper(topicSelected.data.prompt),
        });
        // Do something with the blog post draft like save it or something else...
        await blog.saveDraft(completion.data.choices[0]);
      });
    }
  }
);

Preventing race conditions

The "wait for event" method begins listening for new events from when the code is executed. This means that events sent before the function is executed will not be handled by the wait.

To avoid race condition, always double-check the flow of events going through your functions.


Note: The "wait for event" mechanism will soon provide a "lookback" feature, including events from a given past timeframe.