Example: Build an AI lead agent in 6 API calls

Examples

An end-to-end recipe for building a Gojiberry-style AI sales agent on top of MyAgentMail. The agent watches LinkedIn for buying-intent posts that match a plain-English firing rule, and sends a personalized email the moment a high-intent match arrives.

What you're building

A product where the user (1) connects their LinkedIn account, (2) describes their ICP in plain English, (3) picks how strict the agent should be, and (4) starts receiving emails that turn into conversations — all without writing the LinkedIn scraper, the buying-intent classifier, the dedup logic, or the email sender. MyAgentMail handles those.

What MyAgentMail gives you

  • LinkedIn account connect — drop-in <LinkedInConnect /> React widget or POST /v1/linkedin/sessions. Real LinkedIn data via the customer's own session, not scraping.
  • Plain-English firing ruleintentDescription on every signal. The classifier treats it as authoritative.
  • Real-time watchingPOST /v1/linkedin/signals. Polls the past 24h on a cadence, dedupes by post URL, fires on NEW matches only.
  • Structured matches — every fired match arrives with the post, the author, the classification, and a one-sentence reason citing evidence from the post.
  • Email send + custom domainPOST /v1/inboxes/{id}/send + POST /v1/domains. Reply tracking via WebSocket or webhooks.
  • HMAC-signed webhooks with automatic retries.

What you build yourself

  • A webhook endpoint that receives match payloads (signal.match) and triggers your send logic.
  • The compose step — turning the match into a personalized opener. Most teams use Claude/GPT here.
  • (Optional) A multi-step sequence orchestrator if you want follow-ups. MyAgentMail sends; the cadence is yours.
  • (Optional) Firmographic enrichment if you want company size / industry / HQ on top of the LinkedIn author info we already provide.

The recipe

Six API calls, in order. Every snippet below is real and runnable.

1. Connect a LinkedIn account

Drop the widget into your onboarding flow. The widget never sees your master key — it POSTs through your own server-side proxy.

// app/onboarding/linkedin/page.tsx
"use client";
import { LinkedInConnect } from "@myagentmail/react";
import "@myagentmail/react/styles.css";

export default function ConnectLinkedIn() {
  return (
    <LinkedInConnect
      proxyUrl="/api/myagentmail/linkedin"
      onConnected={async ({ sessionId, label }) => {
        await fetch("/api/onboarding/linkedin-connected", {
          method: "POST",
          body: JSON.stringify({ sessionId, label }),
        });
      }}
    />
  );
}

// app/api/myagentmail/linkedin/[...path]/route.ts
import { linkedInProxyHandler } from "@myagentmail/react/server";
export const { POST } = linkedInProxyHandler({
  apiKey: process.env.MYAGENTMAIL_API_KEY!,
});

2. Capture the user's ICP as a firing rule

Ask the user to describe — in their own words — what kind of post should fire the agent. This becomes intentDescription on the signal. The keyword is just a coarse pre-filter; this rule is what the LLM classifier evaluates against every post.

// Onboarding form (server action)
const intentDescription = `
Flag as ready when the author is a founder or operator at a B2B SaaS
company complaining about cold email being broken, low reply rates, or
their outbound team being burned out. Skip vendors selling outbound tools,
agencies, content marketers, and recruiters posting job ads.
`.trim();

3. Map "precision" to filterMinIntent

Most products show users a binary toggle ("Discovery Mode ↔ High Precision"). Map it to MyAgentMail's three-level filterMinIntent:

const precision = userChose === "high-precision" ? "high" : "medium";
// Discovery Mode → "low"  — webhook on every match the rule accepts
// Balanced       → "medium" — only medium+ intent fires the webhook
// High Precision → "high" — only the strongest matches fire

Note: all matches that the rule accepts are still stored and queryable via GET /v1/linkedin/signals/:id/matchesfilterMinIntent only gates the webhook delivery.

4. (Optional) Seed with a one-shot historical search

Before turning on the recurring watcher, give the user instant gratification by running their rule against the past month and showing 5–10 hits. Same firing rule, different endpoint:

const seed = await mam("POST", "/v1/linkedin/searches", {
  sessionId,
  query: "outbound is broken",
  lookback: "past-month",
  minIntent: precision,
  intentDescription,
  limit: 25,
});
// seed.results is a list of { postUrl, author, classification, postExcerpt }
// Render them in a "here's what your agent will catch" preview screen.

5. Create the signal — agent starts watching

const signal = await mam("POST", "/v1/linkedin/signals", {
  name: `${user.companyName} — buyer intent`,
  query: "outbound",                    // coarse pre-filter
  sessionId,
  cadence: "every_6h",                  // poll 4× a day
  filterMinIntent: precision,
  intentDescription,                    // the firing rule
  webhookUrl: `https://${user.subdomain}.yourapp.com/hooks/intent`,
});

// signal.webhookSecret is shown ONCE — store it server-side, you'll
// need it to verify HMAC on incoming webhooks.
await db.users.update(user.id, { signalWebhookSecret: signal.webhookSecret });

6. Wire the webhook handler — agent emails the lead

// app/hooks/intent/route.ts (Next.js)
import crypto from "node:crypto";

export async function POST(req: Request) {
  const body = await req.text();
  const sig = req.headers.get("x-myagentmail-signature") || "";
  const expected = "v1=" + crypto
    .createHmac("sha256", process.env.SIGNAL_WEBHOOK_SECRET!)
    .update(body)
    .digest("hex");
  if (sig !== expected) return new Response("Bad signature", { status: 401 });

  const event = JSON.parse(body);
  if (event.type !== "signal.match") return new Response("ok");

  const m = event.match;        // post + author + classification
  const opener = await llm.compose({
    task: "personalized_opener",
    post: m.post,
    author: m.author,
    reason: m.classification.reason,
    seller: { product: "Acme outbound platform", positioning: "..." },
  });

  await mam("POST", `/v1/inboxes/${process.env.SENDER_INBOX_ID}/send`, {
    to: m.author.email,           // resolve via your enrichment provider
    subject: opener.subject,
    plainBody: opener.body,
    verified: true,
  });

  return new Response("sent");
}

That's it

Six API calls. Every line of LinkedIn-handling, intent-classification, dedup, retry, signature verification, and email delivery is on us. You wrote: a webhook handler, a compose prompt, and an onboarding form.

Reference implementation

The myagentmail-outreach-starter repo on GitHub implements this exact pattern. Clone it, set MYAGENTMAIL_API_KEY, run npm run dev, and you have a working lead agent on localhost in under five minutes.

What you'd add to make it Gojiberry

If you want to ship something like Gojiberry on top of this:

  • Multi-step sequences — store each sent message and re-send a follow-up after N days if no reply. The Cold outreach with reply tracking recipe in this same KB has the exact code.
  • Firmographic enrichment — combine the LinkedIn author profile we provide with Clearbit / Apollo / your own data for company size, industry, and HQ.
  • A precision UI — wrap filterMinIntent as the binary toggle in your onboarding flow.
  • A "discovery mode" preview screen — call POST /v1/linkedin/searches with lookback: "past-month" in onboarding so users see their first 25 matches before they pay.