PerfCopilot

Email (Gmail / Microsoft 365 / IMAP)

⚠️ Use IMAP for all new email connections. While Google's Restricted-scope verification (CASA) and Microsoft's equivalent are pending, the Gmail and MS Graph OAuth paths are temporarily disabled. IMAP works for Gmail (with an app password), Microsoft 365 (Modern Auth or app password), and every other mailbox. Setup: /docs/integrations/imap/setup. The signal output is identical regardless of transport. Existing OAuth connections continue to work until disconnected.

Email signals capture communication volume and responsiveness — how much the employee drives correspondence, how quickly they reply, and how often they're connecting across team boundaries. The headline metric is emails_sent. Both Gmail (Google Workspace) and Microsoft 365 / Outlook are supported via per-employee OAuth.

Three transports, identical metrics. PerfCopilot supports email via Gmail, Microsoft 365 / Graph, or IMAP for everything else (Fastmail, Zoho, ProtonMail Bridge, self-hosted). The signal output is the same regardless of transport.

Multiple mailboxes per employee. As of 2026-05-11 an employee can connect more than one email source at once (e.g. a personal Gmail plus a work IMAP plus an Outlook account on a third team). All configured adapters run in parallel each cycle and their totals are summed into a single set of email metrics — emails_sent, emails_received, etc. all reflect activity across every connected mailbox. The provider field in the payload becomes a comma-separated list (e.g. "gmail, imap"). PerfCopilot assumes mailboxes don't overlap; cross-mailbox dedup is intentionally not performed.

What we pull

Metadata only. No subject lines. No message bodies. The ingester reads only:

  • From, To, Cc header fields — sender and recipient addresses
  • Date header — timestamp of the message
  • thread_id / conversationId — used to group messages into threads without reading their content

From these headers the ingester computes:

  • emails_sent — count of messages sent in the cycle window
  • thread_initiations — threads where the employee sent the first message
  • cross_team_threads — sent messages where the To/Cc fields contain more than one distinct email domain (a proxy for cross-functional communication)
  • after_hours_sent — messages sent before 8AM or after 7PM
  • avg_reply_time_hours — median hours between receiving a message in a thread and sending a reply in the same thread (only counted for replies within 72 hours)
  • emails_received — count of messages in the inbox in the same window

Both the Gmail and MS Graph adapters cap at 2,000 messages per direction (sent/received) per sync to stay within API quota budgets.

Bulk filter

Newsletters, transactional emails, and mailing list digests inflate the "emails received" count without reflecting real communication. PerfCopilot detects them from four headers — List-Unsubscribe, List-Id, Precedence, Auto-Submitted — and excludes them from emails_received, avg_reply_time_hours, and cross_team_threads. The bulk count is preserved as a separate bulk_received_count field so the AI prompt can mention newsletter volume without it skewing the headline metric.

The filter is the same across Gmail, MS Graph, and IMAP, so switching transports doesn't change what the numbers mean.

Connecting

Email is per-employee. Each employee authorizes their own mailbox separately.

Gmail (Google Workspace)

  1. Each employee goes to their Profile → Connected accounts and connects their Google account.
  2. PerfCopilot requests the gmail.readonly OAuth scope. This grants access to list and read message headers — not bodies.
  3. Managers can also trigger the connection via the Integrations card: send employees a magic-link invite that walks them through the OAuth flow.

Microsoft 365 / Outlook

  1. Same flow — employee connects from Profile → Connected accounts.
  2. PerfCopilot requests the Mail.Read delegated scope via Microsoft identity platform OAuth.
  3. Requires an admin to have pre-consented the Mail.Read scope for your tenant, or each user individually consents during the flow.

Token refresh. Gmail and MS Graph access tokens expire after roughly one hour. PerfCopilot refreshes them automatically at sync time. If a refresh fails (the employee revoked access or the org admin removed the app), the integration is marked inactive and the employee will need to reconnect.

What hits a review

[EMAIL DATA]
emails_sent:          143
thread_initiations:    38
cross_team_threads:    29
after_hours_sent:      12
avg_reply_time_hours:   2.4
emails_received:      201
period_days:           28
provider:             gmail, imap
provider_count:         2

provider is a single slug when only one mailbox is connected (e.g. gmail) and a comma-separated alphabetical list when multiple adapters contributed (e.g. gmail, imap). provider_count is the number of adapters whose data is rolled up into this signal. If one of the configured adapters fails mid-sync (token revoked, IMAP timeout) the rest still run; the failed slug is recorded in a separate provider_errors field so the dashboard can show a partial-sync warning, but the metric totals reflect only the successful adapters.

Plus cohort medians for emails_sent in the [BASELINES] block.

Troubleshooting

"Email signals show zero"

  1. Employee hasn't connected their account. Email is per-employee — no one is opted in by default. Check the employee's profile or the integration card to see if their account shows as connected.
  2. Token expired and refresh failed. The integration card shows a "reconnect needed" state when the refresh token is revoked. The employee needs to re-authorize.
  3. Scope not consented (MS 365). If Mail.Read wasn't pre-consented by your tenant admin, employees see an admin-approval screen during OAuth and can't complete the flow. Your M365 admin needs to grant tenant-wide consent for the Mail.Read scope.

"after_hours_sent count looks wrong"

The ingester defines after-hours as before 8AM or after 7PM in UTC, based on the Date header. This is intentional — the Date header already reflects the sender's local time in most mail clients. If your org uses a non-standard mail system that sends dates in UTC regardless of local time, the after-hours count will be skewed.

"avg_reply_time_hours is missing"

Reply time is computed only when the ingester finds a received message and a sent message in the same thread within 72 hours. Low volume employees (few sent messages or few @-replies to received threads) may have insufficient samples.

Privacy notes

  • Subject lines are never read or stored.
  • Message bodies are never read or stored.
  • Recipient addresses (To, Cc) are stored in raw_signals.payload and used for cross_team_threads computation. They are visible to managers reviewing signals.
  • The Gmail adapter requests the metadata format explicitly, which is a server-side guarantee that body content is excluded from the response.
  • Employees can revoke access at any time from their Google Account or Microsoft account security settings.