The Three Layers of Gmail Analytics
Google Workspace gives admins three distinct ways to look at email data, each with different depth, retention, and programmability:
| Layer | Access Method | Retention | Granularity | Programmatic? |
|---|---|---|---|---|
| Email Log Search | Admin Console UI | 30 days (limited after) | Per-message delivery steps | No |
| Reports API | REST API (activities.list) |
180 days | Per-event (35 event types) | Yes |
| BigQuery Export | SQL queries | Unlimited | Full message metadata + routing | Yes |
Most admins only use the first one — clicking through the Admin Console to troubleshoot "where did this email go?" That's the equivalent of using tail -f on your production logs. The other two layers are where the real analytics live.
Layer 1: Email Log Search (The Flashlight)
Email Log Search (ELS) is the tool most Workspace admins already know. You open the Admin Console, type in a sender or recipient, and see what happened to their messages.
What it shows:
- Message delivery status and post-delivery status
- 5-7 log entries per recipient showing each delivery step
- Spam classification
- Message labels and deletion status
What it doesn't:
- No export, no API, no automation
- Maximum 1,000 messages per search
- After 30 days, you need both the recipient email AND message ID — delivery status is no longer available
- You can't see message contents
ELS is useful for one-off troubleshooting ("did this email arrive?") but useless for systematic analytics. You can't answer "what's our delivery rate this month?" by clicking through 200 individual message lookups.
Layer 2: The Reports API (The Telescope)
The Admin SDK Reports API is where things get interesting for programmatic access. You call activities.list with applicationName=gmail and get back structured event data for every email in your organization.
The API Call
GET https://admin.googleapis.com/admin/reports/v1/activity/users/all/applications/gmail
?eventName=delivery
&startTime=2026-03-01T00:00:00Z
&endTime=2026-03-25T00:00:00Z
&maxResults=200
Required scope: https://www.googleapis.com/auth/admin.reports.audit.readonly
The Event Model
Gmail's Reports API uses a single event name — delivery — with a nested event_info.mail_event_type integer that distinguishes 35 different actions:
| mail_event_type | Event |
|---|---|
| 1 | Message sent |
| 2 | Message received |
| 3 | User manually classified as spam |
| 4 | Gmail flagged as spam post-delivery |
| 5 | Message quarantined |
| 6 | Message released from quarantine |
| 7 | Message opened (first time) |
| 9 | Message replied to (first time) |
| 10 | Message forwarded (first time) |
| 11 | Message auto-forwarded |
| 15 | Link in message body clicked |
| 17 | Attachments downloaded |
| 26 | Message archived |
| 27 | Message permanently deleted |
| 29 | Message saved as draft |
| 30 | Message bounce (delivery failure) |
| 31 | Message viewed (subsequent reads) |
That's not just delivery data. That's a behavioral analytics engine. Google is tracking opens, replies, forwards, link clicks, attachment downloads, archival, and deletion — all surfaced through a single API endpoint.
Constraints
- 30-day maximum window —
startTimeandendTimeare required and can't span more than 30 days. To get a quarter of data, you make 3 calls. - Rate limits — 2,400 queries per minute general, but only 250 filter QPM for queries using
eventName,filters, ororgUnitID. If you hit the limit, Google returns HTTP 503 — use exponential backoff. - Pagination — default page size is 1,000 events, use
nextPageTokento iterate. - Latency — events appear in near real-time (typically a few minutes). Aggregate usage reports lag 1-3 days.
- Data retention — 6 months for audit events via the API. Customer/user usage reports go back 15 months. BigQuery is unlimited.
- Scope — requires a super admin or delegated admin with Reports privileges. This is an admin API, not a user API.
Filtering
The API supports server-side filtering on event parameters:
filters=event_info.mail_event_type==1 // Only "sent" events
filters=event_info.mail_event_type==30 // Only bounces
Operators: ==, <>, <, <=, >, >=. Multiple filters are comma-separated (AND logic).
Aggregate Usage Reports
Beyond per-event activity data, there's a separate usage reports endpoint that gives you daily domain-level metrics:
| Metric | What It Counts |
|---|---|
num_emails_sent |
Outbound emails for the day |
num_emails_received |
Inbound emails for the day |
num_outbound_delivered_emails |
Successfully delivered outbound |
num_outbound_rejected_emails |
Rejected by recipient server |
num_outbound_rerouted_emails |
Rerouted (compliance rules, etc.) |
num_outbound_encrypted_emails |
Sent over TLS |
num_outbound_unencrypted_emails |
Sent without TLS |
num_inbound_spam_emails |
Inbound classified as spam |
num_inbound_non_spam_emails |
Inbound that passed spam filters |
num_1day_active_users |
Users who accessed Gmail today |
This is the 10,000-foot view — useful for dashboards and trend monitoring, not per-message tracking.
Layer 3: BigQuery Export (The Microscope)
If the Reports API is a telescope, BigQuery is a microscope. When you enable Gmail log exports to BigQuery in the Admin Console, every message transaction gets written as a row with 80+ fields of metadata.
This is the full schema. Every field here is queryable with SQL.
Message Metadata
| Field | What It Contains |
|---|---|
message_info.rfc2822_message_id |
The Message-ID header — the key for matching to your own systems |
message_info.subject |
Subject line (may be truncated for very long subjects) |
message_info.payload_size |
Message size in bytes |
message_info.num_message_attachments |
Attachment count |
message_info.is_spam |
Boolean spam classification |
message_info.description |
Human-readable description of what happened |
message_info.action_type |
Delivery action (71 possible values covering every routing step) |
Source and Destination
| Field | What It Contains |
|---|---|
source.address |
Sender's envelope address |
source.from_header_address |
The From: header (what the recipient sees) |
source.from_header_displayname |
Display name in the From: header |
destination.address |
Recipient's email address |
destination.rcpt_response |
SMTP RCPT command response code |
destination.service |
Destination mail service |
flattened_destinations |
All recipients in a single queryable string |
Connection and Encryption
| Field | What It Contains |
|---|---|
connection_info.client_ip |
IP of the sending mail client |
connection_info.smtp_in_connect_ip |
Remote IP for inbound SMTP connections |
connection_info.smtp_out_connect_ip |
Remote IP for outbound SMTP connections |
connection_info.smtp_out_remote_host |
Destination domain or mail server |
connection_info.smtp_reply_code |
SMTP reply code (250 = success, 550 = rejected, etc.) |
connection_info.smtp_tls_state |
0 = not TLS, 1 = TLS |
connection_info.smtp_tls_version |
TLS version (e.g., TLSv1.3) |
connection_info.smtp_tls_cipher |
Cipher suite used |
connection_info.tls_required_but_unavailable |
TLS was required but recipient had no valid cert |
connection_info.failed_smtp_out_connect_ip |
IPs where Gmail failed to connect (MX fallback trail) |
Authentication
| Field | What It Contains |
|---|---|
connection_info.dkim_pass |
DKIM signature verified |
connection_info.spf_pass |
SPF record matched |
connection_info.dmarc_pass |
DMARC policy passed |
connection_info.is_internal |
Sent within your org's domains |
connection_info.is_intra_domain |
Sent within the exact same domain |
connection_info.ip_geo_city |
Geo-located city of sending IP |
connection_info.ip_geo_country |
ISO country code of sending IP |
Spam Analysis
| Field | What It Contains |
|---|---|
spam_info.disposition |
Outcome: delivered, spam folder, rejected, quarantined |
spam_info.classification_reason |
Why: sender reputation, content rules, custom rule, blatant spam (14 possible reasons) |
spam_info.ip_whitelist_entry |
If an IP allowlist influenced the decision |
Attachments and Security
| Field | What It Contains |
|---|---|
attachment.file_name |
File name |
attachment.file_extension_type |
Extension (without the dot) |
attachment.sha256 |
SHA-256 hash |
attachment.malware_family |
Malware classification if detected |
Post-Delivery Behavior
| Field | What It Contains |
|---|---|
post_delivery_info.action_type |
What the user did after delivery (26 possible actions) |
post_delivery_info.interaction.link_url |
URL of clicked link |
post_delivery_info.interaction.drive_id |
Google Drive item accessed |
post_delivery_info.interaction.attachment.file_name |
Attachment that was downloaded/previewed |
Routing Rules and Compliance
| Field | What It Contains |
|---|---|
triggered_rule_info.rule_name |
Name of the compliance/routing rule that fired |
triggered_rule_info.rule_type |
Rule category (28 types: content compliance, routing, DLP, etc.) |
triggered_rule_info.consequence.action |
What the rule did (21 actions: quarantine, reject, modify headers, reroute, etc.) |
triggered_rule_info.string_match.matched_string |
The content that triggered a DLP or compliance rule |
Example Queries
Delivery rate for outbound email this month:
SELECT
COUNTIF(connection_info.smtp_reply_code BETWEEN 200 AND 299) AS delivered,
COUNTIF(connection_info.smtp_reply_code >= 500) AS hard_bounced,
COUNT(*) AS total
FROM `project.dataset.gmail_logs`
WHERE message_info.action_type = 1 -- sent
AND event_info.timestamp_usec > UNIX_MICROS(TIMESTAMP '2026-03-01')
Unencrypted outbound messages (compliance risk):
SELECT
source.address AS sender,
destination.address AS recipient,
connection_info.smtp_out_remote_host AS destination_server
FROM `project.dataset.gmail_logs`
WHERE connection_info.smtp_tls_state = 0
AND connection_info.is_internal = false
AND message_info.action_type = 1
ORDER BY event_info.timestamp_usec DESC
Messages that failed DMARC:
SELECT
source.from_header_address,
connection_info.dkim_pass,
connection_info.spf_pass,
connection_info.dmarc_pass,
connection_info.client_ip
FROM `project.dataset.gmail_logs`
WHERE connection_info.dmarc_pass = false
AND event_info.timestamp_usec > UNIX_MICROS(TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY))
What Google Can't Tell You
The API has clear boundaries:
- No message body content. You get metadata, routing, and behavioral signals — never the actual email text.
- No real-time streaming. Events arrive with a delay (minutes to hours). This isn't a webhook.
- Reports API caps at 30-day windows. BigQuery has no such limit, but requires the export to be enabled in advance — it doesn't backfill.
- Post-delivery tracking only works for Gmail recipients. If you send to a recipient using Outlook, you'll see the SMTP handoff but not whether they opened or clicked. That data comes from Google's own client, not the receiving server.
activities.watchexists but is limited. You can subscribe to push notifications for new events, but the filtering is coarse and the delivery guarantees aren't strong enough for mission-critical workflows.
How We Use This for Outbound Sales
We run AgentCRM on Google Workspace. Every outreach email goes through Gmail — AI-drafted, human-reviewed, sent manually. That means every message we send flows through Google's logging infrastructure, and we can query it.
The Pipeline
Draft created (Gmail API)
|
v
Human reviews + sends
|
v
Reports API polls for delivery events (hourly)
|
v
Match to outreach_log via Message-ID header
|
v
Update delivery_status: delivered / rejected / spam / bounced
|
v
Logo tracking endpoint logs opens (separate system)
|
v
Gmail thread polling detects replies
What We Get From the Admin API Specifically
1. Delivery Confirmation
The Reports API tells us whether the recipient's mail server accepted the message. This is the mail_event_type=1 (sent) event followed by the SMTP response. A 250 from the recipient server means accepted. A 550 means rejected. A bounce notification (mail_event_type=30) means it was accepted but later failed.
This is data Gmail's user-facing API doesn't expose. You can check if a message is in your Sent folder, but you can't ask Gmail "did the recipient's server accept it?" That lives in the Admin Reports API.
2. Bounce Classification
Gmail sends bounce notifications as reply messages in the original thread — our polling cron catches those. But the Admin API catches bounces that don't generate reply messages: silent drops, temporary failures that never recovered, and rejections that happen after the initial SMTP ACK.
Cross-referencing both gives us a more complete picture than either source alone.
3. Encryption Verification
For B2B sales, knowing that your email was transmitted over TLS matters — especially when emailing into healthcare, finance, or government. The BigQuery schema gives us smtp_tls_state, smtp_tls_version, and smtp_tls_cipher for every outbound message. We can verify that sensitive communications were encrypted in transit without trusting the recipient's claim.
4. Authentication Audit
SPF, DKIM, and DMARC results for every message — both inbound and outbound. If our domain's DKIM signing breaks, we'll see dkim_pass=false in the logs before we see deliverability drop in our open rates. This is an early warning system.
The Implementation
Our delivery tracker polls the Reports API via the Google Workspace CLI tool (gws), using the admin profile with admin.reports.audit.readonly scope:
const { stdout } = await execFileAsync(GWS_BIN, [
'admin-reports', 'activities', 'list',
'--params', JSON.stringify({
userKey: 'all',
applicationName: 'gmail',
startTime: oneHourAgo,
maxResults: 200,
}),
], { timeout: 30_000, env: gwsAdminEnv() });
Events come back as structured JSON with nested parameters. We parse out the message_id, sender, recipient, and disposition, then match against our outreach_log table by google_message_id:
const { data: rows } = await supabase
.from('outreach_log')
.select('id, tracking_id, delivery_status')
.eq('google_message_id', event.messageId)
.limit(1);
Matched events update delivery_status and delivered_at on the outreach record, and insert a corresponding email_events row for the audit trail.
What's Next: BigQuery
The Reports API gives us event-level delivery data, but the BigQuery export gives us the full picture — TLS cipher suites, DMARC results, spam classification reasons, routing rule triggers. We're planning to enable the BigQuery export and build:
- Deliverability dashboard — delivery rate, bounce rate, and encryption rate over time, broken out by recipient domain
- Authentication monitoring — alerts when SPF/DKIM/DMARC failures spike, before they impact deliverability
- Recipient server behavior — which domains reject us, which downgrade TLS, which are slow to respond
- Compliance reporting — proof that all messages to regulated industries were encrypted in transit
The data is already being generated. Google is logging every field in that 80-column schema for every message we send. We just need to point BigQuery at it.
The Bigger Picture
Most sales teams treat email as fire-and-forget. Send the message, hope it arrives, check if they replied. The analytics gap between "sent" and "replied" is a black box.
Google Workspace makes that gap transparent — but only if you go beyond the Admin Console UI. The Reports API and BigQuery export turn Gmail from a messaging tool into an instrumented delivery platform. You get SMTP-level delivery confirmation, encryption verification, authentication auditing, spam classification, and post-delivery behavioral analytics — all from the same infrastructure that sends the mail.
The irony is that most organizations paying for Google Workspace Business Plus or Enterprise already have access to this data. They're just not querying it.
Built with AgentCRM — an AI-powered CRM agent running on Google Workspace, Supabase, and Claude.