All pages

Jobs and serverless

track() returns immediately and sends in the background. That is what keeps it off your request’s critical path, and it is also the one thing that can quietly lose data: a process that exits as soon as its work is finished takes the unsent events with it.

A long-lived web server rarely notices, because the next request keeps the process alive. Batch jobs, cron runs, queue workers and serverless handlers all notice, and they notice silently.

Await flush before you exit

TypeScript
import { MarginFuse } from "marginfuse";

const mf = new MarginFuse({ apiKey: process.env.MARGINFUSE_KEY! });

for (const job of await pendingJobs()) {
  await summarize(job);
  mf.track({
    customerId: job.customerId,
    feature: "batch_summary",
    provider: "openai",
    model: "gpt-4.1-mini",
    usage: { inputTokens: job.inputTokens, outputTokens: job.outputTokens },
  });
}

// track() is fire and forget. Without this, the process exits before the
// last events are sent and that traffic is never measured.
await mf.flush();

flush() waits for queued sends and their retries. It never throws, so it is safe in a finally block.

Where it matters

  • Scripts and cron jobs. Await it once at the end.
  • Queue workers. Await it per batch rather than per message, so the background sends still overlap the work.
  • Serverless handlers. The platform can freeze the container the moment your handler resolves. Await it before returning, or use trackAndWait(), which is the same thing for a single event.
  • Next.js route handlers. Same rule. Await before you return the response if the runtime may freeze, otherwise fire and forget is fine.

guard() does not need this on the decision path, because it already awaits the verdict. It still queues the usage report and the acknowledgment afterwards, so a job that uses guard() should flush too.

NextHTTP APIThe four routes, generated from the schemas the server validates against.