You create workflows by describing what you need to the AI in chat. The AI generates the Inngest function, configures the trigger, and wires it into your app.
Creating a workflow with AI
Describe what you need in plain language:
“Create a workflow that syncs our Salesforce contacts every hour.”
“Set up a daily job that sends a Slack summary of open tickets at 9 AM.”
“Add a workflow that sends a welcome email when a new user is added to the database.”
The AI creates the function, sets up the trigger, and registers it. You can view the generated code in the Code tab and test it in the sandbox.
Workflow structure
Workflows are Inngest functions stored in your app’s /inngest/functions directory. Here is a scheduled workflow example:
import { inngest } from './client';
export const syncContacts = inngest.createFunction(
{ id: 'sync-contacts' },
{ cron: '0 * * * *' }, // Every hour
async ({ step }) => {
const contacts = await step.run('fetch-contacts', async () => {
// Fetch contacts from Salesforce
const response = await fetch('https://api.salesforce.com/contacts', {
headers: {
Authorization: `Bearer ${process.env.SALESFORCE_TOKEN}`,
},
});
return response.json();
});
await step.run('update-database', async () => {
// Update local database with fetched contacts
for (const contact of contacts) {
await db.contact.upsert({
where: { externalId: contact.id },
update: { name: contact.name, email: contact.email },
create: { externalId: contact.id, name: contact.name, email: contact.email },
});
}
});
}
);
Each workflow has three parts:
| Part | Purpose |
|---|
| Function config | A unique ID and optional name |
| Trigger | A cron schedule or an event name |
| Handler | The logic that executes, broken into steps |
Cron scheduling
Scheduled workflows use cron expressions:
| Expression | Schedule |
|---|
* * * * * | Every minute |
*/15 * * * * | Every 15 minutes |
0 * * * * | Every hour |
0 9 * * * | Daily at 9:00 AM (UTC) |
0 9 * * 1 | Every Monday at 9:00 AM (UTC) |
0 0 1 * * | First day of every month at midnight |
You don’t need to memorize cron syntax — just tell the AI when you want it to run:
“Run this every weekday at 8 AM.”
Cron schedules use UTC time. If your team operates in a specific timezone, mention it in your prompt and the AI will calculate the correct UTC offset.
Event-driven workflows
Event-driven workflows run in response to events emitted by your app:
import { inngest } from './client';
export const sendWelcomeEmail = inngest.createFunction(
{ id: 'send-welcome-email' },
{ event: 'user/created' },
async ({ event, step }) => {
await step.run('send-email', async () => {
// event.data contains the payload from the event
const { email, name } = event.data;
// Send welcome email logic
});
}
);
To trigger an event-driven workflow, your app emits an event from an API route:
import { inngest } from '@/inngest/client';
// In an API route
await inngest.send({
name: 'user/created',
data: { email: '[email protected]', name: 'Jane' },
});
The AI sets up both the event emission and the workflow handler when you describe the behavior you want.
Step functions
Workflows are broken into steps — discrete units of work managed independently by Inngest. Steps give you:
- Automatic retries — Failed steps are retried without re-running earlier steps
- Durability — Completed steps are not re-executed on retry
- Observability — Each step is tracked separately in the Workflows tab
async ({ step }) => {
// Step 1: Fetch data
const data = await step.run('fetch-data', async () => {
return await fetchFromExternalApi();
});
// Step 2: Process data (only runs if Step 1 succeeds)
const processed = await step.run('process-data', async () => {
return transformData(data);
});
// Step 3: Save results (only runs if Step 2 succeeds)
await step.run('save-results', async () => {
await db.results.createMany({ data: processed });
});
}
Each step should represent a single logical operation — fetching data, transforming it, or saving results.
Error handling and retries
Inngest automatically retries failed steps with exponential backoff, so transient failures like API outages or network timeouts are handled automatically.
If a step continues to fail after all retry attempts, the workflow is marked as failed and error details are visible in the Workflows tab.
For custom error handling, catch errors within the step:
await step.run('fetch-with-fallback', async () => {
try {
return await fetchFromPrimaryApi();
} catch (error) {
// Fall back to cached data
return await fetchFromCache();
}
});
What’s next