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_fieldsevent. The traits become subscriber fields, and Bento creates the subscriber if it does not exist yet. It does not send Bento’s$subscribeevent, so callingidentify()again doesn’t re-run subscribe automations. Automations that fire on field updates can still run. To subscribe someone explicitly, call the Bento SDK’sV1.addSubscriber()directly.track()sends the event name with a$prefix, sosubscription_renewedarrives 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 eventdetails. The event timestamp becomes the Bento eventdate.context.serveris not sent. context.user.traitsbecomes subscriberfieldson every tracked event and page view. Bento fields must be flat, so don’t nest objects in traits.pageView()sends a$viewevent.
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:
siteUuidstring
Your Bento site UUID, used to load the tracking script.
stringBentoServerProvider:
siteUuidstring
Your Bento site UUID.
stringauthentication.publishableKeystring
Bento publishable key.
stringauthentication.secretKeystring
Bento secret key. Keep it server-side only.
stringclientOptions.baseUrl?string
Bento API base URL.
stringhttps://app.bentonow.com/api/v1logErrors?boolean
Make the Bento SDK log the HTTP response of failed requests.
booleanfalsedebug?boolean
Log provider activity to the console.
booleanfalseenabled?boolean
Set to false to disable the provider without removing it.
booleantrueDelivery 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.
