npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

commune-ai

v0.2.2

Published

Our email infrastructure - webhooks, threads, history, and semantic search

Readme

@commune/sdk

Commune is the communication infrastructure for agents. It gives your agent a unified inbox for email + Slack, so your agent can talk to humans where they already work. Most teams get a working integration in ~15 minutes.

Why Commune exists (what it enables)

Agents are powerful, but users already live in email and Slack. Commune bridges that gap so:

  • your agent is reachable where humans already work
  • you don’t have to build deliverability, threading, or Slack plumbing
  • you can ship an agent‑first experience in minutes, not weeks

In practice, Commune lets you:

  • give an agent a real inbox on your domain
  • respond in the correct email or Slack thread every time
  • use conversation state to make smarter, context‑aware replies

How it works (mental model)

  1. Commune receives inbound email/Slack events.
  2. Commune normalizes them into a UnifiedMessage.
  3. Commune sends the UnifiedMessage to your webhook.
  4. Your agent replies using one API call.

By default, the SDK talks to the hosted Commune API. If you self‑host,\n> pass baseUrl to the client.


Quickstart (end‑to‑end in one file)

This is the simplest full flow: receive webhook → run agent → reply in thread.

import express from "express";
import { CommuneClient, createWebhookHandler, verifyCommuneWebhook } from "@commune/sdk";

const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });

const handler = createWebhookHandler({
  verify: ({ rawBody, headers }) => {
    const signature = headers["x-commune-signature"];
    const timestamp = headers["x-commune-timestamp"];
    if (!signature || !timestamp) return false;

    return verifyCommuneWebhook({
      rawBody,
      timestamp,
      signature,
      secret: process.env.COMMUNE_WEBHOOK_SECRET!,
    });
  },
  onEvent: async (message, context) => {
    // Example inbound payload (unified across email + Slack):
    // message = {
    //   channel: "email",
    //   conversation_id: "thread_id",
    //   participants: [{ role: "sender", identity: "[email protected]" }],
    //   content: "Can you help with pricing?"
    // }

    // --- Run your agent here (1–2 line LLM call) ---
    const prompt = `Reply to: ${message.content}`;
    const agentReply = await llm.complete(prompt); // replace with your LLM client

    // Email reply (same thread)
    if (message.channel === "email") {
      const sender = message.participants.find(p => p.role === "sender")?.identity;
      if (!sender) return;

      await client.messages.send({
        channel: "email",
        to: sender,
        text: agentReply,
        conversation_id: message.conversation_id,
        domainId: context.payload.domainId,
        inboxId: context.payload.inboxId,
      });
    }

    // Slack reply (same thread)
    if (message.channel === "slack") {
      const channelId = message.metadata.slack_channel_id;
      if (!channelId) return;

      await client.messages.send({
        channel: "slack",
        to: channelId,
        text: agentReply,
        conversation_id: message.conversation_id, // thread_ts
      });
    }
  },
});

const app = express();
app.post("/commune/webhook", express.raw({ type: "*/*" }), handler);
app.listen(3000, () => console.log("listening on 3000"));

0) Install

npm install @commune/sdk

Unified inbox (what your webhook receives)

Every inbound email or Slack message arrives in this shape:

export interface UnifiedMessage {
  channel: "email" | "slack";
  message_id: string;
  conversation_id: string; // email thread or Slack thread_ts
  participants: { role: string; identity: string }[];
  content: string;
  metadata: { slack_channel_id?: string; ... };
}

API key (required)

All /api/* requests require an API key. Create one in the dashboard and reuse it in your client.

export COMMUNE_API_KEY="your_key_from_dashboard"
export COMMUNE_WEBHOOK_SECRET="your_webhook_secret"
const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });

Context (conversation state)

Commune stores conversation state so your agent can respond with context.

import { CommuneClient } from "@commune/sdk";
const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });

// Thread history (email thread or Slack thread)
const thread = await client.messages.listByConversation(message.conversation_id, {
  order: "asc",
  limit: 50,
});

// All messages from a user
const userHistory = await client.messages.list({
  sender: "[email protected]",
  limit: 25,
});

// All messages in a specific inbox
const inboxMessages = await client.messages.list({
  inbox_id: "i_xxx",
  channel: "email",
  limit: 50,
});

Cross‑channel behavior (email + Slack in one handler)

The same UnifiedMessage shape works for both channels. You only branch on channel.

import express from "express";
import { CommuneClient, createWebhookHandler } from "@commune/sdk";

// Hosted API is default. If self-hosted, pass { baseUrl: "https://your-api" }
const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });

const handler = createWebhookHandler({
  onEvent: async (message, context) => {
    if (message.channel === "email") {
      const sender = message.participants.find(p => p.role === "sender")?.identity;
      if (!sender) return;

      await client.messages.send({
        channel: "email",
        to: sender,
        text: "Got it — thanks for the message.",
        conversation_id: message.conversation_id,
        domainId: context.payload.domainId,
        inboxId: context.payload.inboxId,
      });
    }

    if (message.channel === "slack") {
      const channelId = message.metadata.slack_channel_id;
      if (!channelId) return;

      await client.messages.send({
        channel: "slack",
        to: channelId,
        text: "Replying in thread ✅",
        conversation_id: message.conversation_id,
      });
    }
  },
});

const app = express();
app.post("/commune/webhook", express.raw({ type: "*/*" }), handler);
app.listen(3000);


Setup instructions (dashboard-first)

Domain setup and inbox creation are done in the Commune dashboard. You then copy the IDs into your code.

1) Create a subdomain for the agent

Use a subdomain like agents.yourcompany.com for deliverability and isolation.

2) Create and verify the domain in the dashboard

The dashboard guides you through DNS (SPF/DKIM/MX) and verification.

3) Create inboxes for your agents in the dashboard

Each inbox represents an agent address (e.g. [email protected]).

4) Use IDs from the webhook payload

The webhook payload already includes:

  • domainId (e.g. d_xxx)
  • inboxId (e.g. i_xxx)

Use them when replying:

await client.messages.send({
  channel: "email",
  to: "[email protected]",
  text: "Thanks — replying in thread.",
  conversation_id: message.conversation_id,
  domainId: context.payload.domainId,
  inboxId: context.payload.inboxId,
});

The SDK also supports programmatic domain/inbox creation, but the dashboard flow is the primary path for most teams.

5) Set your webhook secret

When you configure the inbox webhook in the dashboard, Commune shows a webhook secret. Store it as:

export COMMUNE_WEBHOOK_SECRET="your_webhook_secret"

Use it in the verify function shown above.

6) Create an API key in the dashboard

Use the dashboard to create an API key, then set it as:

export COMMUNE_API_KEY="your_key_from_dashboard"

Webhook verification (Commune → your app)

Commune signs outbound webhooks using your inbox webhook secret. Verify the signature before processing the request.

import { createWebhookHandler, verifyCommuneWebhook } from "@commune/sdk";

const handler = createWebhookHandler({
  verify: ({ rawBody, headers }) => {
    const signature = headers["x-commune-signature"];
    const timestamp = headers["x-commune-timestamp"];
    if (!signature || !timestamp) return false;

    return verifyCommuneWebhook({
      rawBody,
      timestamp,
      signature,
      secret: process.env.COMMUNE_WEBHOOK_SECRET!,
    });
  },
  onEvent: async (message) => {
    // handle verified message
  },
});

Full example (single file)

A complete copy‑paste example that:

  • receives webhook
  • replies by email
  • replies in Slack thread
  • fetches conversation history
import express from "express";
import { CommuneClient, createWebhookHandler } from "@commune/sdk";

const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });

const handler = createWebhookHandler({
  onEvent: async (message, context) => {
    // 1) Email reply (same thread)
    if (message.channel === "email") {
      const sender = message.participants.find(p => p.role === "sender")?.identity;
      if (!sender) return;

      await client.messages.send({
        channel: "email",
        to: sender,
        text: "Thanks! We received your email.",
        conversation_id: message.conversation_id,
        domainId: context.payload.domainId,
        inboxId: context.payload.inboxId,
      });

      return;
    }

    // 2) Slack reply (same thread)
    if (message.channel === "slack") {
      const channelId = message.metadata.slack_channel_id;
      if (!channelId) return;

      await client.messages.send({
        channel: "slack",
        to: channelId,
        text: "Thanks! Replying in thread.",
        conversation_id: message.conversation_id,
      });
    }
  },
});

const app = express();
app.post("/commune/webhook", express.raw({ type: "*/*" }), handler);
app.listen(3000, () => console.log("listening on 3000"));