Appearance
Inngest Errors
Inngest automatically handles errors and retries for you. You can use standard errors or use included Inngest errors to control how Inngest handles errors.
Standard errors
All Error objects are handled by Inngest and retried automatically. This includes all standard errors like TypeError and custom errors that extend the Error class. You can throw errors in the function handler or within a step.
typescript
export default inngest.createFunction(
{ id: "import-item-data", triggers: { event: "store/import.requested" } },
async ({ event }) => {
// throwing a standard error
if (!event.itemId) {
throw new Error("Item ID is required");
}
// throwing an error within a step
const item = await step.run('fetch-item', async () => {
const response = await fetch(`https://api.ecommerce.com/items/${event.itemId}`);
if (response.status === 500) {
throw new Error("Failed to fetch item from ecommerce API");
}
// ...
});
}
);All thrown Errors are handled by Inngest and retried automatically. This includes all standard errors like ValueError and custom errors that extend the Exception class. You can throw errors in the function handler or within a step.
python
@client.create_function(
fn_id="import-item-data",
retries=0,
trigger=inngest.TriggerEvent(event="store/import.requested"),
)
async def fn_async(ctx: inngest.Context) -> None:
def foo() -> None:
raise ValueError("foo")
# a retry will be attempted
await ctx.step.run("foo", foo)All Errors returned by your Inngest Functions are handled by Inngest and retried automatically.
go
import (
"github.com/inngest/inngestgo"
"github.com/inngest/inngestgo/step"
)
// Register the function
inngestgo.CreateFunction(
client,
inngestgo.FunctionOpts{
ID: "send-user-email",
},
inngestgo.EventTrigger("user/created", nil),
func(ctx context.Context, input inngestgo.Input[UserCreatedEvent]) (any, error) {
// Run a step which emails the user. This automatically retries on error.
// This returns the fully typed result of the lambda.
result, err := step.Run(ctx, "on-user-created", func(ctx context.Context) (bool, error) {
// Run any code inside a step.
result, err := emails.Send(emails.Opts{})
return result, err
})
if err != nil {
// This step retried 5 times by default and permanently failed.
return nil, err
}
return result, nil
},
)Prevent any additional retries
Use NonRetriableError to prevent Inngest from retrying the function or step. This is useful when the type of error is not expected to be resolved by a retry, for example, when the error is caused by an invalid input or when the error is expected to occur again if retried.
typescript
import { NonRetriableError } from "inngest";
export default inngest.createFunction(
{ id: "mark-store-imported", triggers: { event: "store/import.completed" } },
async ({ event }) => {
try {
const result = await database.updateStore(
{ id: event.data.storeId },
{ imported: true }
);
return result.ok === true;
} catch (err) {
// Passing the original error via `cause` enables you to view the error in function logs
throw new NonRetriableError("Store not found", { cause: err });
}
}
);Parameters
ts
new NonRetriableError(message: string, options?: { cause?: Error }): NonRetriableErrormessage(string): The error message.
options(object): The original error that caused the non-retriable error.
Use NonRetriableError to prevent Inngest from retrying the function or step. This is useful when the type of error is not expected to be resolved by a retry, for example, when the error is caused by an invalid input or when the error is expected to occur again if retried.
python
@client.create_function(
fn_id="import-item-data",
retries=0,
trigger=inngest.TriggerEvent(event="store/import.requested"),
)
async def fn_async(ctx: inngest.Context) -> None:
def step_1() -> None:
raise inngest.NonRetriableError("non-retriable-step-error")
ctx.step.run("step_1", step_1)Use inngestgo.NoRetryError to prevent Inngest from retrying the function. This is useful when the type of error is not expected to be resolved by a retry, for example, when the error is caused by an invalid input or when the error is expected to occur again if retried.
go
import (
"github.com/inngest/inngestgo"
"github.com/inngest/inngestgo/step"
)
// Register the function
inngestgo.CreateFunction(
client,
inngestgo.FunctionOpts{
ID: "send-user-email",
},
inngestgo.EventTrigger("user/created", nil),
func(ctx context.Context, input inngestgo.Input[UserCreatedEvent]) (any, error) {
// Run a step which emails the user. This automatically retries on error.
// This returns the fully typed result of the lambda.
result, err := step.Run(ctx, "on-user-created", func(ctx context.Context) (bool, error) {
// Run any code inside a step.
result, err := emails.Send(emails.Opts{})
return result, err
})
if err != nil {
// This step retried 5 times by default and permanently failed.
// we return a NoRetryError to prevent Inngest from retrying the function
return nil, inngestgo.NoRetryError(err)
}
return result, nil
},
)Retry after a specific period of time
Use RetryAfterError to control when Inngest should retry the function or step. This is useful when you want to delay the next retry attempt for a specific period of time, for example, to more gracefully handle a race condition or backing off after hitting an API rate limit.
If RetryAfterError is not used, Inngest will use the default retry backoff policy.
typescript
inngest.createFunction(
{ id: "send-welcome-sms", triggers: { event: "app/user.created" } },
async ({ event, step }) => {
const { success, retryAfter } = await twilio.messages.create({
to: event.data.user.phoneNumber,
body: "Welcome to our service!",
});
if (!success && retryAfter) {
throw new RetryAfterError("Hit Twilio rate limit", retryAfter);
}
}
);Parameters
ts
new RetryAfterError(
message: string,
retryAfter: number | string | date,
options?: { cause?: Error }
): RetryAfterErrormessage(string): The error message.
retryAfter(number | string | date): The specified time to delay the next retry attempt. The following formats are accepted:number - The number of milliseconds to delay the next retry attempt.string - A time string, parsed by the ms package, such as "30m", "3 hours", or "2.5d".date - A Date object.
options(object): The original error that caused the non-retriable error.
Use RetryAfterError to control when Inngest should retry the function or step. This is useful when you want to delay the next retry attempt for a specific period of time, for example, to more gracefully handle a race condition or backing off after hitting an API rate limit.
If RetryAfterError is not used, Inngest will use the default retry backoff policy.
python
@client.create_function(
fn_id="import-item-data",
retries=0,
trigger=inngest.TriggerEvent(event="store/import.requested"),
)
async def fn_async(ctx: inngest.Context) -> None:
def step_1() -> None:
raise inngest.RetryAfterError("rate-limit-hit", 1000) # delay in milliseconds
ctx.step.run("step_1", step_1)Parameters
python
RetryAfterError(
message: typing.Optional[str],
retry_after: typing.Union[int, datetime.timedelta, datetime.datetime],
) -> Nonemessage(string): The error message.
retry_after(int | datetime.timedelta | datetime.datetime): The specified time to delay the next retry attempt. The following formats are accepted:int - The number of milliseconds to delay the next retry attempt.datetime.timedelta - A time delta object, such as datetime.timedelta(seconds=30).datetime.datetime - A datetime object.
Use RetryAtError to control when Inngest should retry the function or step. This is useful when you want to delay the next retry attempt for a specific period of time, for example, to more gracefully handle a race condition or backing off after hitting an API rate limit.
If RetryAtError is not used, Inngest will use the default retry backoff policy.
go
import (
"github.com/inngest/inngestgo"
"github.com/inngest/inngestgo/step"
)
// Register the function
inngestgo.CreateFunction(
client,
inngestgo.FunctionOpts{
ID: "send-user-email",
},
inngestgo.EventTrigger("user/created", nil),
func(ctx context.Context, input inngestgo.Input[UserCreatedEvent]) (any, error) {
// Run a step which emails the user. This automatically retries on error.
// This returns the fully typed result of the lambda.
result, err := step.Run(ctx, "on-user-created", func(ctx context.Context) (bool, error) {
// Run any code inside a step.
result, err := emails.Send(emails.Opts{})
return result, err
})
if err != nil {
// This step retried 5 times by default and permanently failed.
// We delay the next retry attempt by 5 hours
return nil, inngestgo.RetryAtError(err, time.Now().Add(5*time.Hour))
}
return result, nil
},
)Step errors
After a step exhausts all of its retries, it will throw a StepError which can be caught and handled in the function handler if desired.
ts
inngest.createFunction(
{ id: "send-weather-forecast", triggers: { event: "weather/forecast.requested" } },
async ({ event, step }) => {
let data;
try {
data = await step.run('get-public-weather-data', async () => {
return await fetch('https://api.weather.com/data');
});
} catch (err) {
// err will be an instance of StepError
// Handle the error by recovering with a different step
data = await step.run('use-backup-weather-api', async () => {
return await fetch('https://api.stormwaters.com/data');
});
}
// ...
}
);ts
inngest.createFunction(
{ id: "send-weather-forecast", triggers: { event: "weather/forecast.requested" } },
async ({ event, step }) => {
const data = await step
.run('get-public-weather-data', async () => {
return await fetch('https://api.example.com/data');
})
.catch((err) => {
// err will be an instance of StepError
// Recover with a chained step
return step.run("use-backup-weather-api", () => {
return await fetch('https://api.stormwaters.com/data');
});
});
}
);ts
inngest.createFunction(
{ id: "send-weather-forecast", triggers: { event: "weather/forecast.requested" } },
async ({ event, step }) => {
const data = await step
.run('get-public-weather-data', async () => {
return await fetch('https://api.example.com/data');
})
// This will swallow the error and log it if it's non critical
.catch((err) => logger.error(err));
}
);Callout: Support for handling step errors is available in the Inngest TypeScript SDK starting from version 3.12.0. Prior to this version, wrapping a step in try/catch will not work correctly.
Step errors
After a step exhausts all of its retries, it will throw a StepError which can be caught and handled in the function handler if desired.
python
@client.create_function(
fn_id="import-item-data",
retries=0,
trigger=inngest.TriggerEvent(event="store/import.requested"),
)
async def fn_async(ctx: inngest.Context) -> None:
def foo() -> None:
raise ValueError("foo")
try:
ctx.step.run("foo", foo)
except inngest.StepError:
raise MyError("I am new")Attempt counter
The current attempt number is passed in as input to the function handler. attempt is a zero-index number that increments for each retry. The first attempt will be 0, the second 1, and so on. The number is reset after a successfully executed step.
ts
inngest.createFunction(
{ id: "generate-summary", triggers: { event: "blog/post.created" } },
async ({ attempt }) => {
// `attempt` is the zero-index attempt number
await step.run('call-llm', async () => {
if (attempt < 2) {
// Call OpenAI's API two times
} else {
// After two attempts to OpenAI, try a different LLM, for example, Mistral
}
});
}
);Stack traces
When calling functions that return Promises, await the Promise to ensure that the stack trace is preserved. This applies to functions executing in different cycles of the event loop, for example, when calling a database or an external API. This is especially useful when debugging errors in production.
ts
inngest.createFunction(
{ id: "update-recent-usage", triggers: { event: "app/update-recent-usage" } },
async ({ event, step }) => {
// ...
await step.run("update in db", () => doSomeWork(event.data));
// ...
}
);ts
inngest.createFunction(
{ id: "update-recent-usage", triggers: { event: "app/update-recent-usage" } },
async ({ event, step }) => {
// ...
await step.run("update in db", async () => {
return await doSomeWork(event.data);
});
// ...
}
);Please note that immediately returning the Promise will not include a pointer to the calling function in the stack trace. Awaiting the Promise will ensure that the stack trace includes the calling function.