Skip to content
trakoo
Esc
↑↓navigate↵open⌘Jpreview
On this page

Bento

Route identified user lifecycle events to Bento for email marketing and automation.

Bento is an email marketing and automation platform with built-in event tracking, so behavior in your app can trigger campaigns and segment your audience.

When to use it

Bento is built around identified users and their email addresses. Reach for it when analytics events should feed email:

  • Trigger campaigns from user behavior — onboarding, re-engagement, upgrade nudges.
  • Segment contacts by feature usage and lifecycle stage.
  • Track what a user does after they sign up.

It is not a general-purpose analytics tool. Avoid Bento for:

  • Anonymous page views — the SDK requires an email for every event.
  • High-volume, unidentified event streams.
  • Real-time dashboards.

Pair Bento with a web-analytics provider such as PostHog or Pirsch, and route only identified events to Bento.

Installation

BentoClientProvider ships with trakoo and loads the Bento script from the CDN, so no extra package is needed in the browser. BentoServerProvider ships in the @trakoo/bento package and uses the Bento Node SDK.

# Server-side only
pnpm add trakoo @trakoo/bento @bentonow/bento-node-sdk

Client-side usage

Route Bento to identify and track, and let PostHog cover page views and anonymous traffic. exclude: ['pageView'] does the same job if you prefer to name the method you are dropping.

import { createClientAnalytics } from "trakoo/client";
import { BentoClientProvider } from "trakoo/providers/client";
import { PostHogClientProvider } from "@trakoo/posthog/client";
import { appEvents } from "@/lib/events";

const analytics = createClientAnalytics({
  events: appEvents,
  providers: [
    new PostHogClientProvider({ token: import.meta.env.VITE_POSTHOG_KEY }),
    {
      provider: new BentoClientProvider({
        siteUuid: import.meta.env.VITE_BENTO_SITE_UUID,
      }),
      methods: ["identify", "track"],
    },
  ],
});

await analytics.initialize();

Server-side usage

The same routing applies on the server. Here Pirsch handles page views while Bento receives identified events only.

import { createServerAnalytics } from "trakoo/server";
import { PirschServerProvider } from "trakoo/providers/server";
import { BentoServerProvider } from "@trakoo/bento/server";
import { appEvents } from "@/lib/events";

const serverAnalytics = createServerAnalytics({
  events: appEvents,
  providers: [
    new PirschServerProvider({
      hostname: "example.com",
      clientSecret: process.env.PIRSCH_ACCESS_KEY!,
    }),
    {
      provider: new BentoServerProvider({
        siteUuid: process.env.BENTO_SITE_UUID!,
        authentication: {
          publishableKey: process.env.BENTO_PUBLISHABLE_KEY!,
          secretKey: process.env.BENTO_SECRET_KEY!,
        },
      }),
      exclude: ["pageView"],
    },
  ],
});

Server provider instances are stateless. Calling identify() updates the Bento subscriber, but does not set local identity for later events. Pass an email with every attributed server event:

await serverAnalytics.track(
  "subscription_renewed",
  { plan: "pro" },
  {
    user: { email: "user@example.com" },
  },
);

What the server provider sends

  • identify() sends a $update_fields event. The traits become subscriber fields, and Bento creates the subscriber if it does not exist yet. It does not send Bento’s $subscribe event, so calling identify() again doesn’t re-run subscribe automations. Automations that fire on field updates can still run. To subscribe someone explicitly, call the Bento SDK’s V1.addSubscriber() directly.
  • track() sends the event name with a $ prefix, so subscription_renewed arrives as $subscription_renewed. The browser provider passes the name to Bento.js without a prefix. Keep this in mind when you set up automations that should fire from both sides.
  • For track(), event properties, category, session ID, page, device, and UTM data go into the event details. The event timestamp becomes the Bento event date. context.server is not sent.
  • context.user.traits becomes subscriber fields on every tracked event and page view. Bento fields must be flat, so don’t nest objects in traits.
  • pageView() sends a $view event.

If an event has a value property, Bento requires it to be an object with amount and currency. Bento rejects any other value, and the event is dropped.

Errors and shutdown

When a Bento request fails, or Bento does not queue the event, the provider logs the failure to the console without the event payload and does not throw. identify(), track(), and pageView() still resolve. Set logErrors: true to make the Bento SDK also log the HTTP response of each failed request.

Each call is sent as its own request and awaited, so the provider has nothing to flush. shutdown() releases the Bento client, and the provider ignores calls until initialize() runs again. Shut down a request-owned pair when its request ends, and a reusable pair only at process teardown.

Configuration

BentoClientProvider:

PropType
siteUuidstring

Your Bento site UUID, used to load the tracking script.

Typestring

BentoServerProvider:

PropType
siteUuidstring

Your Bento site UUID.

Typestring
authentication.publishableKeystring

Bento publishable key.

Typestring
authentication.secretKeystring

Bento secret key. Keep it server-side only.

Typestring
clientOptions.baseUrl?string

Bento API base URL.

Typestring
Defaulthttps://app.bentonow.com/api/v1
logErrors?boolean

Make the Bento SDK log the HTTP response of failed requests.

Typeboolean
Defaultfalse
debug?boolean

Log provider activity to the console.

Typeboolean
Defaultfalse
enabled?boolean

Set to false to disable the provider without removing it.

Typeboolean
Defaulttrue

Delivery behavior

  • Bento batches events, so they can take one to three minutes to appear.
  • identify() updates subscriber fields only. Every attributed server event must include a valid email in its current call context.

Resources

Was this page helpful?