Skip to content
← BlogDeveloper

Building an internal ops dashboard on the Norbelys API with an AI agent

A grounded walkthrough of scaffolding a small send-volume and bounce-rate dashboard against the Norbelys API, with an AI coding agent doing the build.

By Gabriel Lara, Developer Relations, Norbelys

Founder-reviewed ·How we research and correct articles

Not every internal need is worth a feature request to your own product team, and not every question deserves a trip through the full Norbelys dashboard. Sometimes what a team actually wants is three numbers, refreshed daily, in the one place they already look: today’s send volume, today’s bounce rate, and which mailboxes need attention. That’s a good-sized project for an AI coding agent — narrow scope, one API, an obvious definition of done — and a good example of building on Norbelys rather than just operating it. This walks the build end to end.

Scoping it before opening the agent

The fastest way to derail an agent-built tool is an underspecified prompt. Before writing anything, pin down exactly three things:

  • What data, precisely. “Today’s sends” and “bounce rate” sound simple until you decide what “today” means across timezones and whether a bounce counts against the day it was sent or the day it bounced. Decide this yourself — don’t let the agent decide it for you mid-build.
  • Where it lives. A tiny static page, a scheduled script that posts to Slack, or a small Cloudflare Worker on a cron trigger are all reasonable — pick one before you start so the agent isn’t guessing at deployment shape while also writing business logic.
  • Who reads it and how often. A once-a-day summary and a live-refreshing dashboard are different builds with different failure tolerances. This one is a once-a-day summary — cheap to build, cheap to be wrong about for a few minutes.

The data source: one door, not three

Every number this dashboard needs comes through Norbelys’s single analytics door, POST /v1/analytics/query — a named-query catalog mapped server-side to the underlying event store, so there’s no separate reporting API to learn. Two entries in that catalog cover this build directly: sender_breakdown for per-mailbox volume and bounce figures, and sender_health for the reputation/status signal that flags a mailbox worth looking at before it drags the rest down. There’s no assembly required across multiple endpoints — one door, two named queries, both scoped to your organization automatically by the credential.

Building it with Claude Code

From spec to a working script

  1. Ground the session

    Start Claude Code in a project with the Norbelys agent kit's AGENTS.md loaded — see the agent-kit and Claude Code guides linked below — so the agent already knows the analytics door and the SDK shape before you type a word.

  2. Give it the narrow spec

    Hand over the three decisions from the scoping step above as constraints, not suggestions: which query names to call, what 'today' means for your timezone, and where the output goes.

  3. Let it scaffold with the SDK

    Ask for a small TypeScript script using @norbelys/sdk that calls the analytics door with sender_breakdown and sender_health, computes the aggregate bounce rate, and formats a short text summary.

  4. Wire the credential correctly

    Point it at a scoped, read-only-in-practice API key stored as an environment variable — this script only ever reads, so there's no reason to hand it anything with broader permissions than that.

  5. Run it against real data and read the output like a skeptic

    Don't just check that it ran without errors — check that the bounce rate it printed matches what you'd get counting by hand for a small, known slice of sends.

  6. Automate the schedule last

    Once the numbers are right on manual runs, wire the cron trigger or scheduled job. Debugging query logic is much easier with an on-demand script than a Worker you have to redeploy to iterate on.

A representative script the agent might produce, after a round of review:

import { Norbelys } from "@norbelys/sdk";

const client = new Norbelys({ apiKey: process.env.NORBELYS_API_KEY });

const startOfToday = new Date();
startOfToday.setUTCHours(0, 0, 0, 0);

const breakdown = await client.analytics.query({
  name: "sender_breakdown",
  params: { since: startOfToday.toISOString() },
});

const health = await client.analytics.query({ name: "sender_health" });

const totalSent = breakdown.data.reduce((sum, row) => sum + row.sent, 0);
const totalBounced = breakdown.data.reduce((sum, row) => sum + row.bounced, 0);
const bounceRate = totalSent > 0 ? (totalBounced / totalSent) * 100 : 0;

const atRisk = health.data.filter((row) => row.status !== "healthy");

const summary = [
  `Sends today: ${totalSent}`,
  `Bounce rate: ${bounceRate.toFixed(2)}%`,
  atRisk.length > 0
    ? `Mailboxes needing attention: ${atRisk.map((s) => s.email).join(", ")}`
    : "All mailboxes healthy",
].join("\n");

console.log(summary);

Field names like sent, bounced, and status above are illustrative of the shape you should expect from a per-sender breakdown and health query — confirm the exact response fields against your SDK’s generated types before shipping this past a manual test run.

Terminal
$ node dashboard.jsSends today: 1,842Bounce rate: 1.14%All mailboxes healthy

Getting it in front of the team

A console.log is a fine milestone, not a finished tool. The lowest-effort last mile is usually a Slack incoming webhook — the script’s summary becomes the body of a POST request, and the schedule that runs the script (a GitHub Actions cron job, or a Cloudflare Worker on a cron trigger if you want it running next to everything else on the platform) is the only new piece of infrastructure the whole project needed. There’s no dashboard framework to stand up for three numbers a team wants to glance at once a day.

If shell scripting is more your team’s habit than TypeScript, the Norbelys CLI covers the same authenticated calls from bash, and an agent can scaffold a cron-driven shell script just as readily as a Node one — pick based on what your team already maintains, not on which is more novel.

Ownership doesn’t disappear because an agent wrote the first draft

Once this is running on a schedule and posting to a channel people actually read, it becomes exactly as load-bearing as any other small piece of internal infrastructure — which means it needs the same things any script that quietly runs unattended needs: an owner who knows it exists, an alert if the scheduled run fails outright rather than silently stops posting, and a note somewhere obvious about which API key it uses so a future credential rotation doesn’t break it without warning. None of that changes because Claude Code wrote the bulk of it. The agent shortened the distance from idea to working script; it didn’t remove the ordinary maintenance a team takes on the moment a tool becomes something people rely on daily.

When this is the wrong tool

Not every question belongs in a standing script. If what you actually want is to ask something once — “what’s our bounce rate been this week?” — that’s a conversation with an MCP-connected agent, not a build: see how to run your whole operation from an AI agent for that pattern. Build the dashboard when the question is recurring and the audience is a team that shouldn’t need to open a chat client to see it; ask the agent directly when the question is one-off and you already have it connected.

Where this connects

This build assumes the setup covered elsewhere in this series: the agent kit for grounding the session, the Claude Code guide for the working pattern, and the auth guide for the credential discipline that matters most once a script like this is running unattended on a schedule. If you’d rather start smaller, the SDK quickstart covers the same @norbelys/sdk install and first call this script builds on.