Delivery & flush()

Every tinymon SDK sends each event the moment you capture it. It only falls back to an in-memory queue when the network fails. This page explains that model and the one situation where you must call flush(): short-lived processes that can exit before an in-flight send completes.

The delivery model

When you call captureException (or it fires automatically from a global handler), the SDK builds the event, scrubs it, runs beforeSend, and immediately issues an HTTP POST to the ingest endpoint. There is no batching delay — the request goes out on the next tick.

For the full state machine — backoff, the retry cap, browser sendBeacon on tab close, and the background worker threads in Python and Ruby — see Transport internals.

Why send-now instead of batching? When an error fires you are usually debugging something live. Waiting on a timer adds latency exactly when you least want it, and any buffered events are lost if the process dies. Sending immediately and only queuing on failure gives you the freshest data with the smallest loss window.

When you need flush()

Because the send is asynchronous and non-blocking, your code keeps running while the request is in flight. In a long-lived process that's fine — there is always time for the request to finish. In a process that exits right after capturing, the runtime can tear down before the socket flushes, and the event is lost. flush() blocks until everything in flight (and anything queued) has been delivered, or a timeout elapses.

Your processCall flush()?Why
Long-running web serverNoThe process stays alive; the background send always completes.
Browser tabNoSends go out immediately; unload is covered by sendBeacon.
Serverless function (Lambda, Cloud Functions, Next.js route)YesThe runtime may freeze the instant you return a response.
CLI tool / one-off scriptYesThe interpreter exits as soon as main returns.
Cron job / queue worker (per-job)SometimesFlush if the worker exits per job; skip if it's a long-lived loop.
Don't sprinkle flush() after every capture in a normal server. It blocks the calling code on a network round-trip for no benefit and adds latency to your request path. Reach for it only at the edge of a short-lived process, right before it exits.

flush() per language

flush() takes an optional timeout and returns once delivery finishes or the timeout is hit — it never throws and never blocks forever.

JavaScript / TypeScript

import { captureException, flush } from 'tinymonjs';

try {
  await doWork();
} catch (err) {
  captureException(err);
  await flush(2000); // wait up to 2s for delivery
  throw err;
}

Python

import tinymonpy

try:
    do_work()
except Exception as e:
    tinymonpy.capture_exception(e)
    tinymonpy.flush(timeout=2.0)  # seconds
    raise

Python also installs an atexit hook that makes a best-effort flush on normal interpreter shutdown, so a script that simply ends usually delivers without an explicit call. Call flush() yourself when you need a hard guarantee (for example, just before a Lambda handler returns).

Ruby

require 'tinymonrb'

begin
  do_work
rescue => e
  Tinymon.capture_exception(e)
  Tinymon.flush(2.0) # seconds
  raise
end

Ruby installs an at_exit hook that drains pending events on shutdown, so the explicit call is only needed when a worker exits per-job before the interpreter would.

Serverless recipes

Next.js Route Handler

Next.js catches errors thrown from Route Handlers before the global handler sees them, so capture explicitly and flush before returning:

import { captureException, flush } from 'tinymonjs';

export async function POST(req) {
  try {
    return await handle(req);
  } catch (err) {
    captureException(err);
    await flush();
    throw err;
  }
}

AWS Lambda (Python)

import tinymonpy
tinymonpy.init(dsn=os.environ['TINYMON_DSN'])

def handler(event, context):
    try:
        return do_work(event)
    except Exception as e:
        tinymonpy.capture_exception(e)
        tinymonpy.flush(timeout=2.0)  # before the runtime freezes
        raise

AWS Lambda (Ruby) / Sidekiq one-shot

require 'tinymonrb'
Tinymon.init(dsn: ENV.fetch('TINYMON_DSN'))

def handler(event:, context:)
  do_work(event)
rescue => e
  Tinymon.capture_exception(e)
  Tinymon.flush(2.0)
  raise
end

Cloudflare Workers

Workers have no global process and freeze after the response. Capture in your handler's catch and keep the worker alive until the send finishes with ctx.waitUntil(flush()):

import { captureException, flush } from 'tinymonjs';

export default {
  async fetch(req, env, ctx) {
    try {
      return await handle(req, env);
    } catch (err) {
      captureException(err);
      ctx.waitUntil(flush());
      return new Response('error', { status: 500 });
    }
  },
};

Gotchas & FAQ

Does flush() guarantee delivery? It guarantees the SDK attempts delivery before returning, up to the timeout. If the network is down for the whole timeout window, queued events remain for the next retry — but in a process that's about to exit, that queue dies with it. Pick a timeout that fits your platform's shutdown budget.

What timeout should I use? 2 seconds is a sensible default. On Lambda, leave headroom under your function timeout. In a Next.js route, the platform's response deadline applies.

Is it safe to call flush() when nothing is pending? Yes — it returns immediately.

Can flush() throw? No. Like every SDK call it swallows its own errors so it can never break your app.