Guide

Log Email to Google Sheets from the Terminal

A spreadsheet is still the fastest way to track leads, signups, or support requests that arrive by email. Wiring that up usually means a brittle Apps Script or a paid Zapier task per row. The Nylas CLI gives you the email side as clean JSON; jq shapes it into rows; the Google Sheets API appends them. This guide builds an email-to-Sheets pipeline you can run on a schedule, with the row mapping in plain code you control.

Written by Pouya Sanooei Software Engineer

VerifiedCLI 3.1.16 · Gmail, Outlook · last tested June 8, 2026

Command references used in this guide: nylas email search, nylas email list, and nylas email read.

How do you log email to Google Sheets?

You log email to a sheet in three steps: pull the messages as JSON, shape them into rows, and append the rows with the Sheets API. The CLI owns the first step — nylas email search --json returns structured messages from any of six providers — so you never parse a mailbox by hand. The last step is one HTTP call to the Sheets values.append endpoint, documented in the Google Sheets API reference.

The middle step is where your logic lives, and keeping it in jq makes the mapping explicit. Each message becomes a row array — date, sender, subject — that the Sheets API accepts directly. Because every layer speaks JSON, there's no scraping and no fragile cell math; the pipeline is three commands you can read in one screen.

# Pull recent leads as JSON (provider-agnostic)
nylas email search "subject:demo request newer_than:1d" --json --limit 50 > leads.json

How do you shape email into rows?

Shape the messages with jq, mapping each one to an array of cells in the order your sheet expects. A row of date, from-address, and subject is a one-line transform, and you can add or reorder columns by editing the array. Because the Sheets API takes a 2D array of values, jq's output drops straight into the request body with no reshaping.

Keep the mapping defensive: messages can have a missing subject or an empty from, so use jq's // "" fallback to avoid null cells. A clean row mapping is the difference between a tidy sheet and one with gaps that break downstream formulas. Validate the output is a JSON array of arrays before sending it.

# Map each message to [date, from, subject], with null-safe fallbacks
jq '[.[] | [ (.date|tostring), (.from[0].email // ""), (.subject // "") ]]' \
  leads.json > rows.json

cat rows.json   # -> [["1718000000","a@x.com","Demo request"], ...]

How do you append rows to the sheet?

Append by POSTing the rows to the Sheets values.append endpoint with an OAuth token for the Sheets scope. The request body wraps your jq output under a values key, and valueInputOption=RAW writes the cells as-is. One call adds every new row to the bottom of the tab, so the sheet grows without you tracking the last row index.

Run the whole pipeline on a schedule — cron, a CI job, or a Kubernetes CronJob — to keep the sheet current. A daily run that appends the day's demo requests costs nothing per row, unlike a per-task automation tool that bills each append. The Google auth here is for Sheets, separate from the mailbox grant the CLI manages.

SHEET_ID="your-spreadsheet-id"
curl -s -X POST \
  "https://sheets.googleapis.com/v4/spreadsheets/$SHEET_ID/values/Sheet1!A1:append?valueInputOption=RAW" \
  -H "Authorization: Bearer $SHEETS_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"values\": $(cat rows.json)}"

Next steps