[{"data":1,"prerenderedAt":460},["ShallowReactive",2],{"\u002Fblog\u002Fhow-to-migrate-off-inngest":3,"\u002Fblog\u002Fhow-to-migrate-off-inngest-surround":451},{"id":4,"title":5,"authors":6,"badge":11,"body":13,"date":441,"description":442,"extension":443,"image":444,"meta":445,"navigation":446,"path":447,"seo":448,"stem":449,"__hash__":450},"posts\u002Fblog\u002Fhow-to-migrate-off-inngest.md","How to migrate off Inngest: moving a scheduled job without downtime",[7],{"name":8,"avatar":9},"Omniyond Team",{"src":10},"\u002Fimages\u002Flogo.png",{"label":12},"Guide",{"type":14,"value":15,"toc":431},"minimark",[16,20,29,38,41,46,49,107,111,114,120,132,135,139,148,174,181,185,204,214,222,235,239,248,273,276,280,286,292,298,302,311,314,393,401,417,421,424,427],[17,18,5],"h1",{"id":19},"how-to-migrate-off-inngest-moving-a-scheduled-job-without-downtime",[21,22,23,24,28],"p",{},"Deciding to ",[25,26,27],"strong",{},"migrate off Inngest"," takes an afternoon. Doing it takes longer, and most of the pain is not in the new tool. It is in the fifteen minutes where the old schedule has been switched off and the new one has not fired yet, and nobody is sure whether the 9am digest is going out tomorrow.",[21,30,31,32,37],{},"This is a practical guide to that cutover. It covers what to write down before you touch anything, how to run both systems at once so you can roll back in seconds, the three things that quietly break, and how to actually turn Inngest off once you are done. It assumes you have already picked a destination. If you have not, the ",[33,34,36],"a",{"href":35},"\u002Fblog\u002Finngest-alternatives","Inngest alternatives roundup"," groups eight options by what they replace.",[21,39,40],{},"All Inngest limits and prices below were checked on 27 August 2026.",[42,43,45],"h2",{"id":44},"step-1-inventory-what-you-are-actually-moving","Step 1: inventory what you are actually moving",[21,47,48],{},"Open your Inngest functions and write down five things for each one. The migrations that go badly are the ones where somebody rebuilt the happy path and forgot the rest.",[50,51,52,71,81,87,93],"ol",{},[53,54,55,58,59,63,64,70],"li",{},[25,56,57],{},"The trigger."," For a cron function this is the full expression including the timezone prefix, for example ",[60,61,62],"code",{},"TZ=Europe\u002FParis 0 12 * * 5",". Copy it exactly. If you set ",[33,65,69],{"href":66,"rel":67},"https:\u002F\u002Fwww.inngest.com\u002Fdocs\u002Fguides\u002Fscheduled-functions",[68],"nofollow","jitter",", note the window: your new tool probably does not have it, and a thundering herd at exactly 12:00:00 is a real outage mode.",[53,72,73,76,77,80],{},[25,74,75],{},"The retry count."," Write down the number in ",[60,78,79],{},"retries:",", and read the next section before you translate it.",[53,82,83,86],{},[25,84,85],{},"Concurrency and flow control."," Per-key concurrency, throttling, debounce, batching. These are the parts people forget they depend on until the replacement fires 400 jobs at once.",[53,88,89,92],{},[25,90,91],{},"Idempotency."," Does the target endpoint tolerate being called twice? Every scheduler here is at-least-once, so if the answer is no, the guard has to live in your handler.",[53,94,95,98,99,102,103,106],{},[25,96,97],{},"What it actually does."," Split the list into two piles: genuine multi-step workflows with ",[60,100,101],{},"step.run",", ",[60,104,105],{},"step.sleep"," and branching, and timers wrapped around one HTTP call. The second pile is usually bigger than people expect, and it is where you have real options.",[42,108,110],{"id":109},"step-2-match-each-function-to-a-destination","Step 2: match each function to a destination",[21,112,113],{},"The two piles go to different places, and trying to send them to the same place is the most common way this migration turns into a rewrite.",[21,115,116,119],{},[25,117,118],{},"Multi-step workflows"," need a durable execution engine. Temporal or Restate, or Trigger.dev if you want Inngest's ergonomics at a lower floor. You are swapping one orchestrator for another and you should budget for it.",[21,121,122,125,126,131],{},[25,123,124],{},"Plain scheduled HTTP calls"," do not need an orchestrator at all. Upstash QStash, a Cloudflare Cron Trigger if you already run Workers, or a pay-per-call HTTP scheduler. This pile is where the cost saving lives: Inngest has nothing between the free tier and ",[33,127,130],{"href":128,"rel":129},"https:\u002F\u002Fwww.inngest.com\u002Fpricing",[68],"$99\u002Fmonth",", so a single daily cron is paying a workflow-platform price for a timer.",[21,133,134],{},"One caution while you plan. Hobby gives you 50,000 executions a month, 5 concurrent steps, and 24 hours of trace retention, and it pauses execution once the quota is gone. \"We will just stay on free during the transition\" is not a plan if the migration doubles your run count for a fortnight.",[42,136,138],{"id":137},"step-3-run-both-systems-one-function-at-a-time","Step 3: run both systems, one function at a time",[21,140,141,142,147],{},"The best public account of this is Monk's write-up of moving ",[33,143,146],{"href":144,"rel":145},"https:\u002F\u002Ftemporal.io\u002Fblog\u002Fhow-monk-migrated-100-workflows-inngest-to-temporal",[68],"100+ workflows from Inngest to Temporal",". Their shape works wherever you are going:",[50,149,150,156,162,168],{},[53,151,152,155],{},[25,153,154],{},"Characterize."," Write tests that lock in what the function does today, before you change anything.",[53,157,158,161],{},[25,159,160],{},"Scaffold."," Build the new path alongside the old one. The important detail: both paths should be thin wrappers around the same domain service, not two implementations of the same logic. Two implementations drift within a week.",[53,163,164,167],{},[25,165,166],{},"Cut over."," Route with a feature flag, keep both live, start with one canary tenant.",[53,169,170,173],{},[25,171,172],{},"Remove."," Delete the Inngest side only after the new path has been stable for a sprint.",[21,175,176,177,180],{},"Two things there are worth stealing outright. They moved a boring scheduled drift check first, precisely because it was boring, which gave them the pattern before they touched anything that mattered. And they watched each migrated job for ",[25,178,179],{},"at least one full firing cycle"," before calling it done. For a daily cron that means waiting a day. Not four hours, and not \"the manual trigger worked\".",[42,182,184],{"id":183},"three-things-that-break-when-you-migrate-off-inngest","Three things that break when you migrate off Inngest",[21,186,187,190,191,194,195,199,200,203],{},[25,188,189],{},"The retry off-by-one."," Inngest's ",[60,192,193],{},"retries: 3"," means three attempts ",[196,197,198],"em",{},"after"," the first failure. Most other systems count the first attempt in their maximum. Monk hit this exactly: the correct mapping to Temporal was ",[60,201,202],{},"maximumAttempts: N + 1",". Get it wrong and your new job gives up sooner than the old one did, quietly, and only on the days something is already broken.",[21,205,206,209,210,213],{},[25,207,208],{},"Cron expressions do not survive contact with interval schedulers."," Inngest takes a real cron expression with a timezone. Several of the cheaper destinations take an interval instead, meaning \"every 24 hours from now\", not \"at 09:00 on weekdays\". A five-field expression like ",[60,211,212],{},"0 9 * * 1-5"," has no interval equivalent. You can anchor a daily job to 09:00 by setting its first run time, but it will also fire on Saturday, and it will drift by an hour when the clocks change. If the schedule genuinely needs cron semantics, keep a cron-native tool such as QStash or a Cloudflare Cron Trigger for that function and move the rest.",[21,215,216,217,221],{},"Inngest's own docs warn that a cron near a DST transition can run ",[33,218,220],{"href":66,"rel":219},[68],"zero, one, or two times"," on the day the clocks change. Whatever you migrate to has the same problem, it just may not tell you about it. If a job must run exactly once a day, make the handler idempotent by date.",[21,223,224,227,228,230,231,234],{},[25,225,226],{},"Code between steps behaves differently."," If you are moving to a replay-based engine, anything non-deterministic between ",[60,229,101],{}," calls, a ",[60,232,233],{},"Date.now()",", a random pick, a direct database read, is re-executed on replay rather than run once. That logic has to move inside a step or an activity. This only bites on the durable-execution path, but it bites hard.",[42,236,238],{"id":237},"step-4-turning-inngest-off-properly","Step 4: turning Inngest off properly",[21,240,241,242,247],{},"Cancelling runs is not the same as stopping them. ",[33,243,246],{"href":244,"rel":245},"https:\u002F\u002Fwww.inngest.com\u002Fdocs\u002Fplatform\u002Fmanage\u002Fbulk-cancellation",[68],"Bulk cancellation"," does not prevent new runs from being enqueued, so if you cancel a cron's in-flight runs and walk away, it fires again on schedule. Use the right lever:",[249,250,251,261,267],"ul",{},[53,252,253,260],{},[25,254,255],{},[33,256,259],{"href":257,"rel":258},"https:\u002F\u002Fwww.inngest.com\u002Fdocs\u002Fguides\u002Fpause-functions",[68],"Pause the function"," when you want a reversible off switch. New events stop triggering it, no deploy or sync is needed, and you can resume any time. You choose whether in-flight invocations pause too, in which case they are cancelled after 7 days if you do not resume, or get cancelled immediately.",[53,262,263,266],{},[25,264,265],{},"Archive the app"," when the whole thing is going. Archiving archives every function in it, new runs stop, in-flight runs finish, and it is reversible.",[53,268,269,272],{},[25,270,271],{},"Then delete the code",", after the new path has run clean for a full cycle. Not before.",[21,274,275],{},"Pause first, archive second, delete third. Each step is reversible and each one gives you a smaller blast radius than the last.",[42,277,279],{"id":278},"what-does-not-come-with-you","What does not come with you",[21,281,282,285],{},[25,283,284],{},"Run history and traces."," Retention is 24 hours on the free tier and 7 days on Pro, so if you want a record of what the job did before the switch, export it while the account is still open. Nobody remembers this until they need it.",[21,287,288,291],{},[25,289,290],{},"Stored events and Replay."," Inngest keeps the events you sent and lets you replay them into a function. That is a real safety net during a migration and it does not exist on the other side of it.",[21,293,294,297],{},[25,295,296],{},"Flow control primitives."," Per-key concurrency, throttle, debounce and batching are Inngest features, not universal ones. If a function relied on \"at most 5 runs per organisation\", check whether your destination can express that before you move it, not after.",[42,299,301],{"id":300},"if-the-function-was-only-ever-a-timer","If the function was only ever a timer",[21,303,304,305,310],{},"Some of your list will turn out to be one HTTP call on a schedule with retries behind it. That is what we built ",[33,306,309],{"href":307,"rel":308},"https:\u002F\u002Fomniyond.com",[68],"Omniyond"," for, so read this knowing who wrote it.",[21,312,313],{},"You point it at a URL, pick once or a recurrence, and it makes the call for $0.01 a run, with a retry ladder of 30 seconds, then 5 minutes, then 30 minutes, and a dead-letter queue behind that. The response is stored for you to fetch or POSTed to a callback:",[315,316,321],"pre",{"className":317,"code":318,"language":319,"meta":320,"style":320},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","curl -X POST https:\u002F\u002Fomniyond.com\u002Fv1\u002Fschedule \\\n  -H \"Authorization: Bearer omni_live_xxx\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"url\":\"https:\u002F\u002Fapi.acme.com\u002Fdaily-digest\",\"schedule\":\"1d\",\"delivery\":\"callback\",\"callbackUrl\":\"https:\u002F\u002Facme.com\u002Fhooks\u002Fdigest\"}'\n","bash","",[60,322,323,346,364,378],{"__ignoreMap":320},[324,325,328,332,336,339,342],"span",{"class":326,"line":327},"line",1,[324,329,331],{"class":330},"sBMFI","curl",[324,333,335],{"class":334},"sfazB"," -X",[324,337,338],{"class":334}," POST",[324,340,341],{"class":334}," https:\u002F\u002Fomniyond.com\u002Fv1\u002Fschedule",[324,343,345],{"class":344},"sTEyZ"," \\\n",[324,347,349,352,356,359,362],{"class":326,"line":348},2,[324,350,351],{"class":334},"  -H",[324,353,355],{"class":354},"sMK4o"," \"",[324,357,358],{"class":334},"Authorization: Bearer omni_live_xxx",[324,360,361],{"class":354},"\"",[324,363,345],{"class":344},[324,365,367,369,371,374,376],{"class":326,"line":366},3,[324,368,351],{"class":334},[324,370,355],{"class":354},[324,372,373],{"class":334},"Content-Type: application\u002Fjson",[324,375,361],{"class":354},[324,377,345],{"class":344},[324,379,381,384,387,390],{"class":326,"line":380},4,[324,382,383],{"class":334},"  -d",[324,385,386],{"class":354}," '",[324,388,389],{"class":334},"{\"url\":\"https:\u002F\u002Fapi.acme.com\u002Fdaily-digest\",\"schedule\":\"1d\",\"delivery\":\"callback\",\"callbackUrl\":\"https:\u002F\u002Facme.com\u002Fhooks\u002Fdigest\"}",[324,391,392],{"class":354},"'\n",[21,394,395,396,400],{},"There is no SDK to install and no code deployed anywhere, which is what makes it a short migration: the function body you already have stays where it is, as an endpoint. It also ships an MCP server, so an agent in Claude Desktop or Cursor can schedule work itself, which is the case we go through in ",[33,397,399],{"href":398},"\u002Fblog\u002Fcron-for-ai-agents","cron for AI agents",".",[21,402,403,404,407,408,412,413,400],{},"The limits, plainly. Recurrences are intervals from ",[60,405,406],{},"5m"," upward, not cron expressions, so weekday-only schedules are out, and the horizon is 90 days. There are no steps, no fan-out and no flow control, so if your list from Step 1 has branching in it, this is the wrong destination and you want the durable-execution pile instead. The head-to-head is in ",[33,409,411],{"href":410},"\u002Fblog\u002Fomniyond-vs-inngest","Omniyond vs Inngest",", and the arithmetic on when per-call beats a subscription is in ",[33,414,416],{"href":415},"\u002Fblog\u002Fpay-per-use-api-for-ai-agents","pay-per-use API pricing for AI agents",[42,418,420],{"id":419},"the-short-version","The short version",[21,422,423],{},"Inventory every function including retries, concurrency and idempotency. Split them into workflows and timers and send the two piles to different tools. Build the new path next to the old one, flag it on for one tenant, and watch a full firing cycle before you believe it. Translate the retry count carefully, expect cron expressions not to port cleanly, and export your history before it ages out. Then pause, archive, delete, in that order.",[21,425,426],{},"Done that way, the risky part is a feature flag you can flip back. Done in one commit on a Friday, it is the 9am digest nobody notices is missing until Monday.",[428,429,430],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":320,"searchDepth":348,"depth":348,"links":432},[433,434,435,436,437,438,439,440],{"id":44,"depth":348,"text":45},{"id":109,"depth":348,"text":110},{"id":137,"depth":348,"text":138},{"id":183,"depth":348,"text":184},{"id":237,"depth":348,"text":238},{"id":278,"depth":348,"text":279},{"id":300,"depth":348,"text":301},{"id":419,"depth":348,"text":420},"2026-08-27","A step-by-step guide to migrate off Inngest: what to inventory first, how to run both systems in parallel, the three things that break during the cutover, and how to actually switch Inngest off.","md",{"src":10},{},true,"\u002Fblog\u002Fhow-to-migrate-off-inngest",{"title":5,"description":442},"blog\u002Fhow-to-migrate-off-inngest","kqD75mFIgwn9J_9p3TqZlSsR_fTs30Fu49wQ-uz7I0Q",[452,456],{"title":453,"path":398,"stem":454,"description":455,"children":-1},"Cron for AI agents: how to make an agent run a task later (2026)","blog\u002Fcron-for-ai-agents","AI agents can decide what to do but not do it later. Here's how to give an agent cron: schedule any HTTP call once or recurring, with retries and delivery.",{"title":457,"path":35,"stem":458,"description":459,"children":-1},"Best Inngest alternatives in 2026: 8 options compared by price and model","blog\u002Finngest-alternatives","A roundup of Inngest alternatives for background jobs, durable workflows and scheduled HTTP calls, grouped by what they actually replace, with every price verified in August 2026.",1787814587323]