# Email Capture

Reads the sellers' e-mail copied to non-reply@, resolves the CRM context and records each e-mail; inactive until set up.

| Platform | Workflow ID | Status | Version | Last saved | Nodes | Systems |
| --- | --- | --- | --- | --- | --- | --- |
| n8n | SAyagoahreVjMNmE | Active | 8bd6ad9b-9bcf-4481-8878-2ae51a9b4d40 | 2026-11-06T19:42:09.676Z | 11 | Communication, CRM, Tooling and Access |

Email Capture keeps the record of e-mail between sellers and customers. Google Workspace copies every external e-mail sent or received by a member of the group `sales-crm@snackprompt.com` to the capture mailbox `non-reply@snackprompt.com`. This workflow reads those copies, finds the seller and the customer in the systems, and hands each e-mail to [Communication — Record Email](/automations/communication-record-email), which writes one Message in Communication and one Interaction per thread in the CRM. It then marks the e-mail in Gmail with a status label. It never creates a person and never deletes mail.

The workflow is **active** since 2026-11-06. [Before activating](#before-activating) keeps the setup checklist and what is still pending. It replaces the older [Email Logging](/automations/email-logging) workflow, which needed one Gmail credential per seller.

The Communication maintainer owns the capture mailbox and the Message records; the CRM maintainer owns the Interactions and the follow-up rules; the Tooling and Access maintainer owns the Tool Account of each seller.

## Flow

Generated from the n8n version in the box above: every box is a node and every arrow is a connection in n8n. Pink pills are triggers, yellow hexagons are If and Switch nodes with their outputs on the arrows, blue cylinders read (Airtable, Data Table, HTTP GET), green cylinders write, purple boxes call another workflow, and dashed arrows are error outputs.

```mermaid
flowchart TD
  subgraph g0["Read the capture mailbox"]
    n1(["Gmail Trigger"]):::trigger
    n0["Config"]:::logic
    n6["List Labels"]:::logic
    n7["Normalize"]:::logic
  end
  subgraph g1["Resolve and record"]
    n9["Resolve CRM Context<br/><small>CRM — Resolve Context</small>"]:::subflow
    n2{{"Known Person?"}}:::decision
    n8["Record Email<br/><small>Communication — Record Email</small>"]:::subflow
  end
  subgraph g2["Status labels"]
    n5["Label Registered"]:::logic
    n4["Label No Match"]:::logic
    n3["Label Error"]:::logic
  end
  n1 --> n0
  n0 --> n6
  n6 --> n7
  n7 --> n9
  n9 --> n2
  n2 -->|true| n8
  n2 -->|false| n4
  n8 --> n5
  n8 -.->|error| n3
  classDef trigger fill:#fde7f3,stroke:#d6409f,color:#111
  classDef decision fill:#fff7e0,stroke:#c99a00,color:#111
  classDef read fill:#e8f1ff,stroke:#3b82f6,color:#111
  classDef write fill:#e7f8ee,stroke:#16a34a,color:#111
  classDef subflow fill:#f3e8ff,stroke:#9333ea,color:#111
  classDef wait fill:#f1f1f1,stroke:#888,color:#111
  classDef logic fill:#ffffff,stroke:#999,color:#111
```

## Systems it touches

This workflow reads and writes no Airtable table itself. It reads Gmail and calls two sub-workflows that do the Airtable work.

| System | Through | Reads | Writes |
| --- | --- | --- | --- |
| Gmail (capture mailbox) | Gmail Trigger, List Labels, Label nodes | Labelled copies, label list | Status labels on each copy |
| Tooling and Access, CRM | [CRM — Resolve Context](/automations/crm-resolve-context) | Tool Account, Membership, People, Deal | Nothing |
| Communication, CRM | [Communication — Record Email](/automations/communication-record-email) | Message, Interaction, Person | Message, Interaction |

## How the copies reach the mailbox

Two routing rules in the Google Workspace Admin Console copy mail for the members of `sales-crm@`: one for outbound mail, delivered to `non-reply+crm-out@`, and one for inbound mail, delivered to `non-reply+crm-in@`. The copy carries three headers: `X-capture-source: external-contacts`, `X-capture-direction` (outbound or inbound) and, on inbound copies, `X-Gm-Original-To` with the seller's address. The customer's copy carries none of them. Gmail filters in the capture mailbox label the copies `CRM/CRM - Outbound` and `CRM/CRM - Inbound` and skip the inbox. Mail between internal domains is not copied.

## Trigger and configuration

The Gmail Trigger polls the capture mailbox every minute with the search `deliveredto:non-reply+crm-out@snackprompt.com OR deliveredto:non-reply+crm-in@snackprompt.com` (the same addresses the Gmail filters use), read and unread, and returns full messages with headers. Each new copy is one item.

Config sets five values that the later nodes read: `mailbox` (the capture address), `internalDomains` (comma-separated company domains), and the names of the three status labels: `labelRegistered`, `labelNoMatch` and `labelError`.

List Labels reads the label list of the mailbox once per run, so Normalize can turn the label names into the ids the Label nodes need.

### What Normalize keeps

| Value | How it is set |
| --- | --- |
| Kept or dropped | Dropped when `X-capture-source` is present and is not `external-contacts`, when the copy was not delivered to a `+crm-` address, or when the copy already carries the label `CRM/CRM - Registered` |
| Direction | `X-capture-direction`; fallback: the `+crm-out` or `+crm-in` delivery address, then the From domain |
| Seller | Outbound: the From address. Inbound: `X-Gm-Original-To`, then the first internal address in To or Cc. Dropped when no internal seller is found |
| Counterparty | Inbound: the sender. Outbound: the first external address in To, then Cc. Dropped when internal or automated (no-reply, mailer-daemon, notifications) |
| External Cc | The other external addresses in To and Cc |
| Internal participants | The other internal addresses in To and Cc, without the seller |
| Message-ID, In-Reply-To | From the headers, without the angle brackets |
| Subject, sent at, body, snippet | Subject cut at 250 characters; the message date or the processing time; plain text cut at 20,000 characters; one line cut at 300 characters |
| Thread link | `https://mail.google.com/mail/u/0/#all/` plus the Gmail thread id of the capture mailbox |
| Label ids | Ids of the three status labels, empty when a label does not exist |

When every copy of a poll is dropped, the run ends at Normalize.

## Resolve and record

Resolve CRM Context calls [CRM — Resolve Context](/automations/crm-resolve-context) once per e-mail with the seller address, the counterparty address and name. It returns the seller's Membership and company, the person, the person's company and the open Deal, plus notes.

Known Person? sends the e-mail on only when a person was found in the CRM People mirror. Record Email then calls [Communication — Record Email](/automations/communication-record-email) with the normalized e-mail and the context. That sub-workflow writes the Message and the Interaction and carries the tag `email-log-n8n`.

## Status labels

| Outcome | Label | Effect |
| --- | --- | --- |
| Message and Interaction written | `CRM/CRM - Registered` | Done |
| Person not in the CRM People mirror | `CRM/CRM - No match` | Nothing written; review the person, then remove the label to reprocess |
| Record Email failed | `CRM/CRM - Error` | Remove the label and let the trigger pick the copy up again |

The Label nodes add the label to the copy in the capture mailbox. When a label name in Config does not exist in Gmail, the Label node has no id to add and continues without error. No node removes labels, marks mail as read or deletes mail: the Interaction's Evidence URL points to the thread in this mailbox.

## Nodes

The workflow has 11 nodes: 10 that run and one sticky note. For an If node, output 0 is true and output 1 is false. Record Email has a second output for errors.

| Node | Type | What it does |
| --- | --- | --- |
| Gmail Trigger | gmailTrigger | Polls the capture mailbox every minute for the two capture labels |
| Config | set | Mailbox, internal domains and the three status label names |
| List Labels | gmail | Reads the label list once per run |
| Normalize | code | One item per capture copy; reads the capture headers; drops what is not a customer contact |
| Resolve CRM Context | executeWorkflow | Calls CRM — Resolve Context per e-mail |
| Known Person? | if | Continue only when the counterparty is a CRM person |
| Record Email | executeWorkflow | Calls Communication — Record Email; error output on failure |
| Label Registered | gmail | Adds CRM/CRM - Registered to the copy |
| Label No Match | gmail | Adds CRM/CRM - No match to the copy |
| Label Error | gmail | Adds CRM/CRM - Error to the copy |
| Sticky Note | stickyNote | Setup note on the canvas; does not run |

## Connections

| From | Output | To |
| --- | --- | --- |
| Gmail Trigger | 0 | Config |
| Config | 0 | List Labels |
| List Labels | 0 | Normalize |
| Normalize | 0 | Resolve CRM Context |
| Resolve CRM Context | 0 | Known Person? |
| Known Person? | 0 | Record Email |
| Known Person? | 1 | Label No Match |
| Record Email | 0 | Label Registered |
| Record Email | 1 | Label Error |

## Settings and error handling

| Setting | Value |
| --- | --- |
| Status | Active since 2026-11-06 |
| Execution order | v1 |
| Error workflow | None |
| On error | Record Email: continue on the error output. Label nodes: continue on the regular output. Other nodes: stop the workflow (default) |
| Sub-workflow calls | One call per item, waiting for the result |
| Caller policy | Workflows from the same owner |
| Execution data retention | Instance default; this workflow sets none |

## Before activating

| Step | Where | State at the last review |
| --- | --- | --- |
| The Gmail Trigger, List Labels and the three Label nodes use the OAuth credential of `non-reply@snackprompt.com` | n8n | Done: all five use the credential `no-reply mail` |
| The labels `CRM/CRM - Registered`, `CRM/CRM - No match` and `CRM/CRM - Error` exist in the capture mailbox | Gmail | Done |
| The routing rules and the Gmail filters are in place and tested | Google Workspace | Configured; the five tests in the setup document are pending |
| Every member of `sales-crm@` has a Tool Account (Google Workspace, Login Alias = e-mail) with an Identity Binding | Tooling and Access | Pending |
| The two sub-workflows exist and are reachable | n8n | Created, inactive; a sub-workflow does not need to be active to be called |
| The Channel of `non-reply@` is Active | Communication | Done |

## Known gaps

These come from the node configuration and the Google setup.

| Gap | Effect |
| --- | --- |
| A customer only in Bcc of an outbound e-mail is not in the copy's headers | That e-mail gets no counterparty and is dropped |
| Only the first external address is the counterparty | A message to several customers is recorded on one of them; the others go to Receivers (Person) or stay as `cc:` in About Keys |
| Mail between internal domains is not copied by Google | Internal threads are never recorded, by design |
| "Today" and "plus three days" use the UTC date | E-mail processed late at night in Brazil gets the next day |
| The thread link opens in the capture mailbox | Only the mailbox admin can open the Evidence URL |
| Bounces are suppressed on the capture address | A broken routing rule fails silently; [Email Capture — Health Check](/automations/email-capture-health-check) covers this |

## Recover a partial write

| What was saved | What to do |
| --- | --- |
| Copy labelled `CRM/CRM - Error` | Fix the cause, remove the label; the next poll reprocesses the copy and the sub-workflow updates in place |
| Copy labelled `CRM/CRM - No match` | Create or find the person in Core, wait for the CRM mirror, remove the label |
| Message written, Interaction missing | Remove the status label; Record Email updates the Message and creates the Interaction by thread link |

## Keep this page in sync

The box at the top shows the n8n version this page describes. Open the workflow in n8n: when its last save is newer than the date in the box, this page is out of date until the next review. Every node and every connection of that version is in [Nodes](#nodes) and [Connections](#connections).