Handling Failures

Define any failure handlers for your function with the onFailure option. This function will be automatically called when your function fails after it's maximum number of retries. Alternatively, you can use the "inngest/function.failed" system event to handle failures across all functions.

import { inngest } from "./client";

export default inngest.createFunction(
  {
    id: "import-product-images",
    onFailure: async ({ error, event, step }) => {
      // This is the failure handler which can be used to
      // send an alert, notification, or whatever you need to do
    },
  },
  { event: "shop/product.imported" },
  async ({ event, step, runId }) => {
    // This is the main function handler's code
  }
);

The failure handler is very useful for:

  • Sending alerts to your team
  • Sending metrics to a third party monitoring tool (e.g. Datadog)
  • Send a notification to your team or user that the job has failed
  • Perform a rollback of the transaction (i.e. undo work partially completed by the main handler)

Failures should not be confused with Errors which will be retried. Read the error handling & retries documentation for more context.


How onFailure works

The onFailure handler is a helper that actually creates a separate Inngest function used specifically for handling failures for your main function handler.

The separate Inngest function utilizes an "inngest/function.failed" system event that gets sent to your account any time a function fails. The function created with onFailure will appear as a separate function in your dashboard with the name format: "<Your function name> (failure)".

onFailure({ error, event, step, runId })

The onFailure handler function has the same arguments as the main function handler when creating a function, but also received an error argument.

error

The JavaScript Error object as thrown from the last retry in your main function handler.

The Inngest SDK attempts to serialize and deserialize the Error object to the best of its ability and any custom error classes (e.g. Prisma.PrismaClientKnownRequestError or MyCustomErrorType) that may be thrown will be deserialized as the default Error object. This means you cannot use instance of within onFailure to infer the type of error.

event

The "inngest/function.failed" system event payload object. This object is similar to any event payload, but it contains data specific to the failed function's final retry attempt. See the complete reference for this event payload here.

step

See the step reference in the create function documentation.

runId

This will be the function run ID for the error handling function, not the function that failed. To get the failed function's run ID, use event.data.run_id. Learn more about runId here.

Examples

Send a Slack notification when a function fails

In this example, the function attempts to sync all products from a Shopify store, and if it fails, it sends a message to the team's #eng-alerts Slack channel using the Slack Web Api's chat.postMessage (docs) API.

import { client } from "@slack/web-api";
import { inngest } from "./client";

export default inngest.createFunction(
  {
    id: "sync-shopify-products",
    // Your handler should be an async function:
    onFailure: async ({ error, event }) => {
      const originalEvent = event.data.event;

      // Post a message to the Engineering team's alerts channel in Slack:
      const result = await client.chat.postMessage({
        token: process.env.SLACK_TOKEN,
        channel: "C12345",
        blocks: [
          {
            type: "section",
            text: {
              type: "mrkdwn",
              text: `Sync Shopify function failed for Store ${
                originalEvent.storeId
              }: ${error.toString()}`,
            },
          },
        ],
      });

      return result;
    },
  },
  { event: "shop/product_sync.requested" },
  async ({ event, step, runId }) => {
    // This is the main function handler's code
    const products = await step.run("fetch-products", async () => {
      const storeId = event.data.storeId;
      // The function might fail here or...
    });
    await step.run("save-products", async () => {
      // The function might fail here after the maximum number of retries
    });
  }
);

Capture all failure errors with Sentry

Similar to the above example, you can capture and all failed functions' errors and send them to a singular place. Here's an example using Sentry's node.js library to capture and send all failure errors to Sentry.

import * as Sentry from "@sentry/node";
import { inngest } from "./client";

Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
});

export default inngest.createFunction(
  {
    name: "Send failures to Sentry",
    id: "send-failed-function-errors-to-sentry"
  },
  { event: "inngest/function.failed" },
  async ({ event, step }) => {

    // The error is serialized as JSON, so we must re-construct it for Sentry's error handling:
    const error = event.data.error;
    const reconstructedEvent = new Error(error.message);
    // Set the name in the newly created event:
    // You can even customize the name here if you'd like,
    // e.g. `Function Failure: ${event.} - ${error.name}`
    reconstructedEvent.name = error.name;

    // Add the stack trace to the error:
    reconstructedEvent.stack = error.stack;

    // Capture the error with Sentry and append any additional tags or metadata:
    Sentry.captureException(reconstructedEvent,{
      extra: {
        function_id,
      },
    });

    // Flush the Sentry queue to ensure the error is sent:
    return await Sentry.flush();
  }
);

Additional examples