openclaw/extensions/googlechat/PROACTIVE-MESSAGING.md
root 560d31b0ff feat(googlechat): add proactive messaging support
Add space caching and auto-resolution for proactive Google Chat messages:

- Auto-cache space IDs when users message the bot (knownSpaces)
- Auto-resolve users/{id} to cached spaces for outbound messages
- Fallback to findDirectMessage API when not cached
- Add comprehensive documentation for proactive messaging patterns

This enables cron jobs, skills, and agents to send proactive messages
to users who have previously interacted with the bot.

Fixes: Users can now send messages via 'users/{id}' target instead of
needing to know the space ID beforehand.
2026-01-29 07:06:47 +00:00

6.7 KiB

Google Chat Proactive Messaging

This document describes how to send proactive (initiated by the bot) messages in Google Chat, rather than only responding to incoming webhooks.

Overview

Google Chat is unique among Moltbot channels because it operates purely via webhooks rather than persistent connections. This creates challenges for proactive messaging, but we've implemented several features to make it possible:

  1. Space ID Persistence - Auto-cache space IDs when users message the bot
  2. Auto-Resolve - Automatically resolve users/{id} to cached spaces
  3. findDirectMessage Fallback - Use Google Chat API to find DM spaces
  4. Manual Space Targeting - Send directly to known space IDs

How It Works

Automatic Space Caching

When a user messages your bot, Moltbot automatically caches the space mapping:

{
  "channels": {
    "googlechat": {
      "knownSpaces": {
        "users/123456789": {
          "spaceId": "spaces/AAAAxxxx",
          "displayName": "John Doe",
          "type": "DM",
          "lastSeenAt": 1706515200000
        }
      }
    }
  }
}

This cache persists across restarts and enables proactive messaging to users who have previously interacted with the bot.

Sending Proactive Messages

If the user has messaged your bot before, you can use their user ID:

moltbot message send \
  --channel googlechat \
  --to "users/123456789" \
  --text "Hello! This is a proactive message."

Moltbot will:

  1. Check the knownSpaces cache for the user
  2. If found, send to the cached space
  3. If not found, call findDirectMessage API
  4. If still not found, show an error with instructions

Method 2: Using Space ID

If you know the space ID (from a previous message or Google Chat UI):

moltbot message send \
  --channel googlechat \
  --to "spaces/AAAAxxxx" \
  --text "Hello space!"

Method 3: Via Cron/Heartbeat

Schedule proactive messages in your config:

{
  "cron": {
    "jobs": [
      {
        "schedule": "0 9 * * *",
        "text": "Good morning! Your daily reminder.",
        "target": "users/123456789",
        "channel": "googlechat"
      }
    ]
  }
}

Or for spaces:

{
  "cron": {
    "jobs": [
      {
        "schedule": "0 9 * * MON",
        "text": "Weekly standup time!",
        "target": "spaces/AAAAxxxx",
        "channel": "googlechat"
      }
    ]
  }
}

Method 4: From Skills/Agents

Skills can send proactive messages using the message tool:

// In a skill
await message.send({
  channel: "googlechat",
  target: "users/123456789", // or "spaces/AAAAxxxx"
  text: "Proactive notification from skill!"
});

Target Formats

Google Chat supports several target formats:

Format Example Description
User ID users/123456789 Google Chat user resource name
Email users/user@example.com User's email address
Space ID spaces/AAAAxxxx Google Chat space resource name
With prefix googlechat:users/123 Explicit channel prefix

Troubleshooting

"No Google Chat DM found for users/xxx"

This error means:

  1. The user has never messaged your bot, OR
  2. The space cache was lost, AND
  3. The findDirectMessage API couldn't locate a DM

Solutions:

  • Ask the user to message your bot first
  • Use the space ID directly if you know it
  • Check your service account permissions

Service Account Permissions

Your Google Chat service account needs these scopes:

  • https://www.googleapis.com/auth/chat.bot

For findDirectMessage to work, the service account must be added to the Google Chat space or have domain-wide delegation (for Workspace admins).

Checking Cached Spaces

To see cached spaces in your config:

moltbot config get channels.googlechat.knownSpaces

Clearing Space Cache

If you need to clear the cache (e.g., spaces were renamed):

moltbot config set channels.googlechat.knownSpaces '{}'

Limitations

  1. DMs require prior interaction - You cannot initiate a DM with a user who has never messaged your bot
  2. Bot must be in space - For group/room messages, the bot must be a member
  3. Service account limits - Some API features require domain-wide delegation in Google Workspace

Comparison with Other Channels

Feature WhatsApp Telegram Google Chat
Initiate DMs Yes Yes No*
Persistent connection Yes Yes No
Requires webhook No Optional Yes
Space caching N/A N/A Auto

*Google Chat requires the user to message the bot first, or use findDirectMessage which may not always work

API Reference

resolveGoogleChatOutboundSpace

async function resolveGoogleChatOutboundSpace(params: {
  account: ResolvedGoogleChatAccount;
  target: string;
  cfg?: MoltbotConfig;
  useCache?: boolean;        // default: true
  useFindDirectMessage?: boolean;  // default: true
}): Promise<string>

Resolves a target (user ID or space ID) to a space ID for sending messages.

getCachedSpaceForUser

function getCachedSpaceForUser(
  cfg: MoltbotConfig,
  userId: string,
  accountId?: string
): GoogleChatKnownSpace | undefined

Retrieves cached space info for a user.

Space Cache Schema

type GoogleChatKnownSpace = {
  spaceId: string;        // "spaces/AAAAxxxx"
  displayName?: string;   // User or space name
  type?: "DM" | "ROOM";   // Space type
  lastSeenAt?: number;    // Timestamp
};

Best Practices

  1. Let users message first - Design flows where users initiate contact
  2. Cache proactively - Store space IDs when users message, before you need them
  3. Handle errors gracefully - Always wrap proactive sends in try-catch
  4. Use space IDs for critical messages - More reliable than user ID resolution
  5. Document your flows - Users should know why they're getting messages

Examples

Welcome Message After Pairing

// In pairing approval handler
await message.send({
  channel: "googlechat",
  target: `users/${userId}`,
  text: "You're now paired! I'll send you daily summaries at 9 AM."
});

Daily Digest

{
  "cron": {
    "jobs": [{
      "schedule": "0 9 * * *",
      "text": "📊 Your daily digest:\n- 3 new emails\n- 2 PRs awaiting review",
      "target": "users/xxx",
      "channel": "googlechat"
    }]
  }
}

Error Notifications

// In a skill or agent
try {
  await performTask();
} catch (error) {
  await message.send({
    channel: "googlechat",
    target: `users/${adminUserId}`,
    text: `⚠️ Task failed: ${error.message}`
  });
}