LOOMAL
Examples

Inbox classifier

Sort every unread message into a category with Claude, then apply labels for downstream queries

A Buyer agent that reads unread mail, asks Claude to pick a category, and applies labels per category. Once labeled, you can query the inbox by category — labels: "sales", labels: "high-priority" — without re-running the classifier.

Before you start

  • A project at console.loomal.ai with Mail turned on.
  • Your Loomal API key (loid-...) and an Anthropic API key (sk-ant-...).
npm install @loomal/sdk @anthropic-ai/sdk

The classifier

The label scheme below is just one example. Labels are free-form on Loomal — pick a vocabulary that matches your team's pipeline.

ClassificationLabels applied
salessales, high-priority
supportsupport
meetingmeeting
newsletternewsletter, low-priority
spamspam, low-priority
classifier.ts
import { Loomal } from "@loomal/sdk";
import Anthropic from "@anthropic-ai/sdk";

const loomal = new Loomal({ apiKey: process.env.LOOMAL_API_KEY! });
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });

const CATEGORIES = ["sales", "support", "meeting", "newsletter", "spam"] as const;
type Category = (typeof CATEGORIES)[number];

async function classify(subject: string, body: string): Promise<Category> {
  const completion = await anthropic.messages.create({
    model: "claude-haiku-4-5-20251001",
    max_tokens: 16,
    system: `Classify the email into exactly one of: ${CATEGORIES.join(", ")}. Reply with the single word only.`,
    messages: [
      { role: "user", content: `Subject: ${subject}\n\n${body}` },
    ],
  });
  const text = completion.content
    .filter((b): b is Anthropic.TextBlock => b.type === "text")
    .map((b) => b.text)
    .join("")
    .trim()
    .toLowerCase();
  return (CATEGORIES.includes(text as Category) ? text : "support") as Category;
}

function labelsFor(category: Category): string[] {
  if (category === "sales") return ["sales", "high-priority"];
  if (category === "newsletter") return ["newsletter", "low-priority"];
  if (category === "spam") return ["spam", "low-priority"];
  return [category];
}

async function run() {
  const { messages } = await loomal.mail.listMessages({
    limit: 50,
    labels: "unread",
  });

  for (const m of messages) {
    const category = await classify(m.subject, m.extractedText || m.text);

    await loomal.mail.updateLabels(m.messageId, {
      addLabels: ["read", "classified", ...labelsFor(category)],
      removeLabels: ["unread"],
    });

    console.log(`${m.from[0]} -> ${category}`);
  }
}

run();

Run with LOOMAL_API_KEY=loid-... ANTHROPIC_API_KEY=sk-ant-... npx tsx classifier.ts.

Query the results

Once classified, pull each category with a single call:

const sales = await loomal.mail.listMessages({ labels: "sales", limit: 50 });
const urgent = await loomal.mail.listMessages({ labels: "high-priority", limit: 50 });

On this page