EmitKit
Server-only event notifications and activity feeds with channel routing and rich metadata.
EmitKit sends events to named channels with rich metadata, so your team can watch activity feeds and get notified when something important happens.
When to use it
- Real-time activity feeds and internal dashboards for team visibility.
- Notifications for high-signal events like signups, payments, or errors.
- Event logging with user context, routed into channels by topic.
EmitKit is not a product-analytics dashboard. Pair it with a provider like PostHog or Pirsch when you need charts and funnels.
Installation
The EmitKit provider ships in the @trakoo/emitkit package and uses the EmitKit JavaScript SDK.
pnpm add trakoo @trakoo/emitkit @emitkit/js
The provider needs version 3 of the EmitKit SDK.
Client-side usage
EmitKit is server-only — its SDK has no browser package. To capture browser events, use the Proxy to forward them to your server, then send them to EmitKit from there. See Browser events via proxy below.
Server-side usage
import { createServerAnalytics } from "trakoo/server";
import { EmitKitServerProvider } from "@trakoo/emitkit/server";
import { appEvents } from "@/lib/events";
export const serverAnalytics = createServerAnalytics({
events: appEvents,
providers: [
new EmitKitServerProvider({
apiKey: process.env.EMITKIT_API_KEY!, // starts with 'emitkit_'
channelName: "general",
}),
],
});
Server provider instances are stateless. Calling identify() updates the remote EmitKit profile, but does not set local identity for later events. Pass the current request’s user id or email with every attributed server event and page view.
EmitKit merges the traits you pass into the profile’s stored properties: new values replace old ones, and properties you leave out are kept.
Track events with properties and optional user context. EmitKit accepts multiple identifiers per user, including an id, email, or username.
await serverAnalytics.track(
"purchase_completed",
{
orderId: "order-123",
amount: 99.99,
currency: "USD",
},
{
userId: "user-456",
user: {
email: "user@example.com",
traits: { plan: "pro" },
},
},
);
Channel routing
Channels group events into separate streams, similar to Slack channels. The provider resolves the channel for each event in this order:
- Per-event override — a
__emitkit_channelvalue in the event properties (highest priority). - Category mapping — the event’s category matched against
categoryChannelMap. - Default channel — the
channelNameoption (falls back to'general').
Map categories to channels so events route automatically:
new EmitKitServerProvider({
apiKey: process.env.EMITKIT_API_KEY!,
channelName: "general", // fallback for unmapped categories
categoryChannelMap: {
user: "user-activity",
error: "alerts",
conversion: "revenue",
},
});
// Category 'user' → 'user-activity'
await serverAnalytics.track("user_signed_up", { plan: "pro" });
Override the channel for a single event by adding __emitkit_channel. trakoo strips this key before sending, so it never appears in the event metadata.
await serverAnalytics.track("large_payment", {
amount: 50000,
__emitkit_channel: "critical-payments",
});
Notifications
The notify option decides whether EmitKit sends a push notification for each event. A single event overrides it with a boolean __emitkit_notify property, so the provider can stay quiet by default and notify only for the events that matter, or the other way round:
new EmitKitServerProvider({
apiKey: process.env.EMITKIT_API_KEY!,
notify: false, // feed entries only
});
// This one also sends a push notification.
await serverAnalytics.track("large_payment", {
amount: 50000,
__emitkit_notify: true,
});
Only a boolean counts; any other value falls back to notify. trakoo strips __emitkit_notify before sending, like __emitkit_channel. The Better Auth plugin sets both hints on every auth event.
What EmitKit receives
Each tracked event becomes one EmitKit event:
- Title: the event name in title case, so
user_signed_upbecomes “User Signed Up”. - Icon: chosen from the event category, so feeds stay legible without extra configuration.
- Description: the event’s
descriptionproperty, or a short default for the built-in categories. - Tags: the event category plus a
tagsproperty made of strings, without duplicates. - Metadata: the event properties without
__emitkit_channeland__emitkit_notify, pluscategory,timestamp,sessionId, and the page, device, UTM, and server context. trakoo removesipfrom the device and server context, so the visitor IP address that proxy ingestion records never reaches your feeds. User traits are not attached to events. - User: the email from the current call’s user context, then its user id, then the event’s
userId. - Notification: the event’s
__emitkit_notifywhen it is a boolean, otherwise thenotifyoption. - Source:
trakoo.
EmitKit stores at most 16 KB per event across all of these fields, metadata included, and rejects a larger event with payload_too_large. The description and tags properties are also part of the metadata, so they count twice toward that limit.
Page views
pageView() sends a silent “Page View” event (notify: false unless its properties set __emitkit_notify, displayed as a message) to the __emitkit_channel in its properties, the channel mapped to the navigation category, or the default channelName, in that order.
EmitKit allows 100 requests per minute per API key by default. Page views forwarded through the proxy can use up the budget your signup and payment events need. If you only want explicit events in EmitKit, exclude page views with routing:
export const serverAnalytics = createServerAnalytics({
events: appEvents,
providers: [
{
provider: new EmitKitServerProvider({
apiKey: process.env.EMITKIT_API_KEY!,
}),
exclude: ["pageView"],
},
],
});
Errors and rate limits
trakoo turns off the EmitKit SDK’s retries, so a call never waits longer than the timeout. A request that hits the rate limit (HTTP 429), fails, or times out is not delivered. A failed track() is logged by trakoo without affecting other providers. Failed identify() and pageView() calls are logged and do not reject. The provider’s log line names the EmitKit error code, HTTP status, and request id, for example EmitKitError rate_limited 429 request …, so you can tell an invalid API key (unauthorized) from a validation error (validation_error) or a rate limit (rate_limited). A request that got no response is logged as timeout or network_error, without a status.
Browser events via proxy
Send browser events through your own API, then forward them to EmitKit on the server.
import { createClientAnalytics } from "trakoo/client";
import { ProxyProvider } from "trakoo/providers/client";
import { appEvents } from "@/lib/events";
export const analytics = createClientAnalytics({
events: appEvents,
providers: [new ProxyProvider({ endpoint: "/api/analytics" })],
});
await analytics.track("button_clicked", { buttonId: "signup" });
import { createServerAnalytics } from "trakoo/server";
import { ingestProxyEvents } from "trakoo/providers/server";
import { EmitKitServerProvider } from "@trakoo/emitkit/server";
import { appEvents } from "@/lib/events";
const serverAnalytics = createServerAnalytics({
events: appEvents,
providers: [
new EmitKitServerProvider({
apiKey: process.env.EMITKIT_API_KEY!,
channelName: "user-activity",
}),
],
});
export async function POST(request: Request) {
await ingestProxyEvents(request, serverAnalytics);
return new Response("OK");
}
Configuration
apiKeystring
Your EmitKit API key. Starts with 'emitkit_'.
stringtimeout?number
Request timeout in milliseconds. A request that fails or times out is not retried.
number5000channelName?string
Default channel when no override or category mapping applies.
stringgeneralcategoryChannelMap?Record<string, string>
Map event categories to specific channels.
Record<string, string>notify?boolean
Send a notification for each event. An event's boolean `__emitkit_notify` property overrides it.
booleantruedisplayAs?'message' | 'notification'
How events appear in EmitKit.
'message' | 'notification'notificationdebug?boolean
Log debug output for the provider.
booleanfalseenabled?boolean
Enable or disable the provider.
booleantrueBest practices
- Route by category with
categoryChannelMapand reserve__emitkit_channelfor the few events that need a dedicated channel. - Name channels by domain —
payments,user-signups,team-alerts— so feeds stay scannable. identify()updates a remote profile only; provide the current request’s user id or email on every attributed server event and page view.- Include email and username in user traits so events resolve to the same person across identifiers.
- Exclude
pageViewunless you want every page view in a feed. Page views count against the same rate limit as your important events. - Run EmitKit alongside an analytics provider: EmitKit for feeds and notifications, PostHog or Pirsch for dashboards. See routing to control which events reach each provider.
