Back to The Times of Claw

DenchClaw Inter-App Messaging: Connect Your Custom Apps

How DenchClaw's inter-app messaging lets your custom Dench Apps communicate with each other in real time via the events system.

Mark Rachapoom
Mark Rachapoom
·6 min read
DenchClaw Inter-App Messaging: Connect Your Custom Apps

DenchClaw Inter-App Messaging: Connect Your Custom Apps

DenchClaw's inter-app messaging system lets custom Dench Apps publish and subscribe to events, enabling them to communicate with each other in real time. When App A takes an action — filtering contacts, selecting a record, completing an analysis — App B can react to it immediately without any manual coordination.

This is the foundation for building multi-app workspaces where specialized apps collaborate.

The Events System for Inter-App Communication#

DenchClaw's dench.events bridge API is the backbone of inter-app communication. Apps can:

  • Emit custom events with a payload
  • Subscribe to events from any app or from the DenchClaw system
  • React to selections, filters, or actions in other apps
// App A: Emit an event
await dench.events.emit("contacts:selected", {
  contactIds: ["abc-123", "def-456"],
  source: "contact-browser"
});
 
// App B: Subscribe to that event
dench.events.on("contacts:selected", (payload) => {
  const { contactIds } = payload;
  loadContactDetails(contactIds);
});

Any app running in the DenchClaw workspace receives events broadcast by any other app. Events are namespaced by convention (appname:action) to avoid collisions.

Real-World Example: Contact Browser + Email Composer#

Here's a two-app workflow that uses inter-app messaging:

App 1: Contact Browser (contact-browser.dench.app)

// When user selects contacts in the browser
document.getElementById('contact-list').addEventListener('change', async (e) => {
  const selected = getSelectedContacts();
 
  // Emit selection event
  await dench.events.emit("contacts:selected", {
    contacts: selected.map(c => ({
      id: c.id,
      name: c['Full Name'],
      email: c['Email'],
      company: c['Company']
    }))
  });
});

App 2: Email Composer (email-composer.dench.app)

// Listen for contact selections from any other app
dench.events.on("contacts:selected", (payload) => {
  const { contacts } = payload;
 
  // Populate recipient list
  populateRecipients(contacts);
 
  // Show notification
  document.getElementById('status').textContent =
    `${contacts.length} recipients loaded from Contact Browser`;
});

Now the two apps are connected: select contacts in the browser, they instantly appear as recipients in the composer. No shared state file, no polling, no manual copy-paste.

Event Payload Structure#

Events follow a standard structure:

{
  type: "contacts:selected",   // Event name
  source: "contact-browser",   // App that emitted it
  timestamp: 1711450800000,    // Unix timestamp
  payload: {                   // App-specific data
    contacts: [...],
    filter: "Status = Active"
  }
}

Listeners receive the full event object, so they know the source app and can filter accordingly.

System Events from DenchClaw#

Beyond app-to-app events, DenchClaw broadcasts system events that any app can subscribe to:

EventDescriptionPayload
entry:createdNew CRM entry created{objectName, entryId, fields}
entry:updatedCRM entry fields changed{objectName, entryId, changedFields}
entry:deletedCRM entry deleted{objectName, entryId}
view:changedActive CRM view changed{objectName, viewName, filters}
selection:changedUser selected entries{objectName, entryIds}
chat:messageNew AI chat message{role, content}
agent:thinkingAI is processing{task}
agent:doneAI task completed{result}

Subscribe to system events to build reactive apps:

// Update a chart whenever CRM data changes
dench.events.on("entry:created", async (event) => {
  if (event.payload.objectName === "deals") {
    await refreshChart();
  }
});
 
// Show a spinner when the AI is thinking
dench.events.on("agent:thinking", () => {
  showSpinner();
});
 
dench.events.on("agent:done", () => {
  hideSpinner();
});

Event-Driven Dashboard Updates#

The most powerful use of system events: build a dashboard that updates in real time as CRM data changes, without polling.

// Connect to real-time CRM events
dench.events.on("entry:created", refreshMetrics);
dench.events.on("entry:updated", refreshMetrics);
dench.events.on("entry:deleted", refreshMetrics);
 
async function refreshMetrics() {
  const pipeline = await dench.db.query(`
    SELECT "Stage", COUNT(*) as count, SUM(CAST("Deal Value" AS FLOAT)) as total
    FROM v_deals WHERE "Status" = 'Active'
    GROUP BY "Stage"
  `);
 
  updateChart(pipeline);
}

Your pipeline dashboard now updates the moment a deal status changes — no refresh button, no polling interval.

Scoped Event Subscriptions#

Subscribe to events from a specific source app only:

// Only listen to events from the contact-browser app
dench.events.on("contacts:selected", handler, {
  source: "contact-browser"
});
 
// Only listen to entry events for the deals object
dench.events.on("entry:updated", handler, {
  filter: (event) => event.payload.objectName === "deals"
});

One-Time Event Listeners#

Listen for an event only once (useful for waiting for an async operation to complete):

// Wait for a specific agent task to complete
await new Promise((resolve) => {
  dench.events.once("agent:done", (event) => {
    if (event.payload.taskId === myTaskId) resolve(event.payload.result);
  });
});

Emitting Events from the AI Agent#

The AI agent can also emit events to running apps. This allows the AI to push updates directly to your custom UI:

"Update the pipeline dashboard with the Q1 analysis results."

The agent computes the analysis and emits:

dench.events.emit("analysis:complete", {
  type: "q1-pipeline-analysis",
  data: analysisResults
});

Your dashboard app subscribes to this event and renders the results.

Event History and Replay#

DenchClaw maintains a short in-memory event history (last 100 events). When a new app loads, it can replay recent events to sync its state:

// On app load, catch up with recent events
const recent = await dench.events.getHistory({ limit: 50 });
recent.forEach(event => processEvent(event));
 
// Then subscribe to future events
dench.events.on("*", processEvent);

See also: DenchClaw Event System for the full system event reference, and DenchClaw Store API for persistent cross-app state.

Frequently Asked Questions#

Do events persist when apps are closed?#

Events are in-memory only — they're not persisted to disk. If an app is closed and reopened, it misses events from the time it was closed (unless you use the event history replay feature for recent events).

Can events from one DenchClaw workspace reach another workspace?#

No. Events are scoped to the running workspace instance. Cross-workspace messaging would require webhooks or a shared database.

How do I debug inter-app events?#

Open the browser developer tools on any DenchClaw app. DenchClaw logs all events to the console with timestamps. You can also subscribe to the wildcard event "*" in development to log all events: dench.events.on("*", console.log).

Is there a limit on event payload size?#

Keep payloads under 1MB for reliable delivery. Large payloads (image data, full CRM dumps) should be stored in DuckDB or the filesystem and referenced by ID in the event payload — don't pass large data blobs directly through events.

Can I use events for cross-user communication in a multi-user workspace?#

In a shared workspace (multi-user setup), events are broadcast to all connected clients. This enables real-time collaboration — one user's selection in App A appears in everyone's App B. Access control for events is on the multi-user roadmap.

Ready to try DenchClaw? Install in one command: npx denchclaw. Full setup guide →

Mark Rachapoom

Written by

Mark Rachapoom

Building the future of AI CRM software.

Continue reading

DENCH

© 2026 DenchHQ · San Francisco, CA