Google Chat Unread Messages Brief with Workspace Studio and Gemini

Google Chat Unread Messages Brief
Google Chat Unread Messages Brief

Coming to work every morning often means facing a wall of unread Google Chat messages from the night before across colleagues in different time zones. Skimming through every space and thread to catch up takes time.

To make my mornings easier, I built an automated Unread Messages Brief directly inside Google Chat—powered by Google Workspace Studio, Gemini, and a Google Apps Script Custom Step (with zero Google Cloud Console setup required). Everything was built with the help of Gemini 4 and Antigravity, which made figuring out the Google Chat read-state API nuances and wiring up the Workspace Studio custom step effortless.

In this post, I’ll share how it works, the design decisions behind it, and how you can set it up for yourself using my google-chat-unread-brief repository.

Why not just use Google Chat’s “Ask Gemini” tab?

At this point, you might be wondering: Can’t you just use the built-in “Ask Gemini” tab in Google Chat?

I had the same question and gave it a try, but ran into two main limitations:

  1. Too much noise from background threads: When you ask the built-in Ask Gemini tab to summarize your unread messages, it includes all unread messages across every background thread—even threads you aren’t following. I wanted a brief that filters strictly for top-level unread messages and messages from threads I actively follow.
  2. No scheduled summaries: There is no way to schedule an automatic daily summary in the Ask Gemini tab so that it’s waiting for you when you start your day.

What does Unread Messages Brief do?

With Google Workspace Studio and a small Google Apps Script custom step, the workflow gives you:

  • Daily 7:45 AM Scheduled Brief: Automatically summarizes your unread messages from the last 24 hours (top-level messages and threads you follow, without marking them as read) and posts a clean bulleted brief with clickable space links into your private Unread Messages Brief space each weekday morning.
  • On-Demand Trigger (/briefnow): Type /briefnow in your Unread Messages Brief space anytime during the day to generate a fresh summary on demand.

Let’s look at how to set it up step by step.

Step 1: Create your private space in Google Chat

First, create a dedicated space in Google Chat where your briefs will be posted:

  1. Open Google Chat.
  2. Click New chat (+) → Create a space.
  3. Name the space Unread Messages Brief (keep it private with just yourself).

Step 2: Set up & install the Apps Script Custom Step

While Google Workspace Studio has built-in steps for Google Chat and Gemini, we need a custom step in Google Apps Script to query the Google Chat API’s read state (getSpaceReadState and getThreadReadState) and fetch only the unread top-level messages and followed threads without marking them as read.

ℹ️ Note

Why isn’t there a “Mark all unread messages as read” command? While reading unread state (spaces.list, messages.list, getSpaceReadState, getThreadReadState) works out of the box with Apps Script’s default project, the Google Chat API blocks write calls (updateSpaceReadState) unless you create, link, and configure a standard Google Cloud project in the Cloud Console. Keeping it read-only avoids any Google Cloud project setup.

Here’s how to set up and install the custom step:

  1. Go to script.google.com and click New project (name it Unread Messages Brief).
  2. Enable the manifest file:
    • Click Project Settings (⚙️ icon on the left sidebar).
    • Check the box “Show ‘appsscript.json’ manifest file in editor”.
  3. Copy the files from my google-chat-unread-brief repository into the editor:
    • Click Editor (< > icon on the left sidebar).
    • Open appsscript.json and replace its contents with appsscript.json.
    • Open Code.gs and replace its contents with Code.gs.
    • Click Save (💾).
  4. Authorize the Script in Apps Script:
    • In the top toolbar dropdown (next to Run / Debug), select onExecuteFetchUnread and click Run.
    • Approve the Google OAuth permissions prompt when asked.
  5. Install Test Deployment for Workspace Studio:
    • Click Deploy (top right) → Test deployments.
    • Next to Application(s): Google Workspace Add-on, click Install.
    • Click Done.

Let’s take a quick look at what’s happening under the hood in both files.

First, appsscript.json declares the Google Chat Advanced Service (Chat v1), read-only OAuth scopes, and exposes a custom workflow action (Fetch Unread Chat Messages) to Google Workspace Studio with two inputs (excludeSpaceName and maxLookbackHours) and one output (unreadMessagesText):

"flows": {
  "workflowElements": [
    {
      "id": "fetch_unread_chat_messages",
      "state": "ACTIVE",
      "name": "Fetch Unread Chat Messages",
      "description": "Fetches unread Google Chat messages across your spaces with space links, without marking them as read.",
      "workflowAction": {
        "inputs": [
          {
            "id": "excludeSpaceName",
            "description": "Name or Space ID of your Unread Messages Brief space to exclude from the summary (optional)",
            "cardinality": "SINGLE",
            "dataType": { "basicType": "STRING" }
          },
          {
            "id": "maxLookbackHours",
            "description": "Maximum hours to look back for unread messages (e.g. 24)",
            "cardinality": "SINGLE",
            "dataType": { "basicType": "INTEGER" }
          }
        ],
        "outputs": [
          {
            "id": "unreadMessagesText",
            "description": "Formatted unread messages and space links ready for Gemini summarization",
            "cardinality": "SINGLE",
            "dataType": { "basicType": "STRING" }
          }
        ],
        "onConfigFunction": "onConfigFetchUnread",
        "onExecuteFunction": "onExecuteFetchUnread"
      }
    }
  ]
}

Second, Code.gs implements the configuration UI (onConfigFetchUnread), the execution handler (onExecuteFetchUnread), and the core fetchUnreadChatData function. For each active space within the lookback window, it checks your space-level lastReadTime and then inspects each thread’s lastReadTime so that only unread top-level messages and replies in threads you follow are included:

// Get the user's space-level read state
const readState = Chat.Users.Spaces.getSpaceReadState(
  `users/me/${space.name}/spaceReadState`
);
const spaceLastReadMs = new Date(readState.lastReadTime).getTime();
const lastActiveMs = new Date(space.lastActiveTime).getTime();

// Skip if the space is already read
if (lastActiveMs <= spaceLastReadMs) continue;

// Fetch candidate messages newer than spaceLastReadTime (within lookback window)
const msgResponse = Chat.Spaces.Messages.list(space.name, {
  filter: `createTime > "${effectiveSinceIso}"`,
  pageSize: 40
});

// Filter to ONLY top-level messages + replies in new or followed threads
for (const m of candidateMessages) {
  const trs = Chat.Users.Spaces.Threads.getThreadReadState(
    `users/me/${m.thread.name}/threadReadState`
  );
  const threadLastReadMs = (trs && trs.lastReadTime)
    ? new Date(trs.lastReadTime).getTime()
    : null;

  const isTopLevel = !m.threadReply;
  const isReplyToNewUnreadThread = newTopLevelThreads.has(m.thread.name);
  const isFollowedThread = threadLastReadMs !== null && threadLastReadMs > spaceLastReadMs;

  if (isTopLevel || isReplyToNewUnreadThread || isFollowedThread) {
    // Format sender, message text, and space link for Gemini
  }
}

Finally, onExecuteFetchUnread packages the formatted messages and space links into the unreadMessagesText output variable using AddOnsResponseService so the next step in the flow can pass it directly to Gemini.

Step 3: Build the on-demand flow in Google Workspace Studio

Next, open Google Workspace Studio (refresh the page if it was already open so your newly installed Unread Messages Brief custom step appears).

💡 Tip

Google Workspace Studio displays each output variable using its description from appsscript.json (for example, unreadMessagesText appears in the + Variables picker under Fetch Unread Chat Messages → Formatted unread messages and space links ready...).

Create a new flow named Unread Messages Brief with the following steps:

  1. Starter (Chat → When I get a chat message):
    • Conversation type: Choose Spaces & named group DMs.
    • Spaces: Select Unread Messages Brief.
    • Enter /briefnow in the “Message has” field and choose “Match text exactly”.
  2. Step 2 (Chat → Post in a space — Progress Indicator):
    • Select your Unread Messages Brief space and paste:
      ⏳ _Scanning unread messages and generating your brief. Please wait..._
      
  3. Step 3 (Custom Step → Fetch Unread Chat Messages):
    • Click + Add step → scroll to Unread Messages Brief → select Fetch Unread Chat Messages.
    • Set Exclude Space Name or ID to Unread Messages Brief.
    • Set Max Lookback Window (Hours) to 24.
  4. Step 4 (Ask Gemini — Summarize):
    • Add an Ask Gemini step with the following prompt:
      Provide a high-level summary of my unread Google Chat messages below, formatted as a bulleted list grouped by Space.
      - If the input says NO_UNREAD_MESSAGES, just reply: "🎉 You're all caught up! No unread Google Chat messages."
      - Format each Space as a bullet point starting with the Space name in bold (* **Space Name**:) followed by an overall summary whose length is proportional to the discussion (keep it brief for 1–2 messages, and more detailed for longer discussions).
      - Focus on the topic and outcome rather than listing who said what. Only mention a person's name (in bold, e.g. **Aaron**) when attribution is genuinely useful—such as a specific announcement, question, or action item—and omit names entirely for general group discussions or quick updates.
      - At the end of each Space's bullet item, include a clickable chat icon linked to its exact Link from the input: [💬](https://chat.google.com/room/...).
      - Do not add any concluding remarks or trailing blank lines after the last bullet item.
      
      Unread Messages Data:
      
    • On the last line under Unread Messages Data:, click + Variables and select Step 3: Fetch Unread Chat Messages → Formatted unread messages and space links ready....
  5. Step 5 (Chat → Post in a space):
    • Select your Unread Messages Brief space and format the message with the title ☕ *Unread Messages Brief*, followed by the Step 4: Ask Gemini → Content created by Gemini variable, and the footer 💡 _/briefnow to refresh summary_.
  6. Click Turn on.

Here’s what the complete on-demand flow looks like:

On-Demand Unread Messages Brief Flow

Step 4: Test the flow & schedule the daily 7:45 AM brief

To test the flow, mark any conversation in Google Chat as unread, head over to your Unread Messages Brief space, and send /briefnow.

Within seconds, you’ll see the progress indicator followed by your ☕ Unread Messages Brief with bold space names, high-level topic summaries, and a clickable 💬 link at the end of each item that jumps straight to that space—while all your unread badges in the sidebar remain intact:

Unread Messages Brief in Google Chat
Unread Messages Brief in Google Chat

Once your /briefnow flow is working, you can easily create the daily morning schedule by making a copy of the flow (since Google Workspace Studio only allows one Starter per flow):

  1. In your My flows list, click More (⋮) next to Unread Messages Brief and select Make a copy.
  2. Rename it to Unread Messages Brief - Scheduled.
  3. Change the Starter at the top to Schedule → Every weekday at 7:45 AM (and delete the ⏳ progress step in Step 2 so it only posts the finished brief), then click Turn on.
Scheduled Unread Messages Brief Flow

Wrapping up

In this post, we built an automated Unread Messages Brief for Google Chat that delivers both a scheduled morning summary and an on-demand /briefnow summary without marking your messages as read.

For the full code and step-by-step instructions, check out the repository on GitHub:


See also