Legacy — Email Logging
Superseded by Email Capture on 2026-11-06. Old per-mailbox design; inactive, never published.
- Platform
- n8n · Open in n8n
- Status
- Inactive
- Workflow ID
YDwJwKWtayHrH0mC- Nodes
- 24
- n8n version
ef290697-9cbf-46c6-ad18-73a6f6980857- Last saved in n8n
- 2026-11-06T16:58:26.654Z
Email Logging keeps a record of e-mail with customers. Every message in one Gmail mailbox, received or sent, becomes a Message in Communication. When the other person has an open Relationship in the CRM, the whole thread also becomes one Interaction on that Relationship, and the follow-up date moves: reply today when they wrote, check again in three days after the first message you sent. Mail between internal addresses and automatic senders is skipped. The workflow never creates a person.
The workflow is inactive. It has never been published in n8n, so nothing on this page is running yet. Before activating lists what has to be in place first. One copy of the workflow serves one mailbox.
Superseded. On 2026-11-06 this design was replaced by Email Capture and its two sub-workflows, which read one capture mailbox (non-reply@snackprompt.com) fed by Google Workspace routing instead of one Gmail credential per seller, and write Deals instead of Relationships. This workflow stays saved and inactive in n8n; this page describes it as saved, Relationship steps included, and will be removed when the workflow is deleted.
The Communication maintainer owns the Message records and the mailbox setup; the CRM maintainer owns the Interactions and the follow-up rules.
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 source
flowchart TD
subgraph g0["Read the mailbox"]
n13(["Gmail Trigger"]):::trigger
n0["Config"]:::logic
n17["Normalize"]:::logic
end
subgraph g1["Resolve the context"]
n6[("Find Mailbox Account<br/><small>GET Tooling Tool Account</small>")]:::read
n20["Resolve Seller"]:::logic
n8[("Find Membership (CRM)<br/><small>GET CRM Membership</small>")]:::read
n7[("Find Membership (Communication)<br/><small>GET Communication Membership</small>")]:::read
n4[("Find Channel<br/><small>GET Communication Channel</small>")]:::read
n11[("Find Person (CRM)<br/><small>GET CRM People</small>")]:::read
n19["Resolve Person"]:::logic
n10[("Find Person (Communication)<br/><small>GET Communication Person</small>")]:::read
n9[("Find Message<br/><small>GET Communication Message</small>")]:::read
n5[("Find Interaction<br/><small>GET CRM Interaction</small>")]:::read
n12[("Find Relationships<br/><small>GET CRM Relationship</small>")]:::read
n18["Prepare"]:::logic
end
subgraph g2["Communication Message"]
n16{{"Message Exists?"}}:::decision
n22[("Update Message<br/><small>PATCH Communication Message</small>")]:::write
n3[("Create Message<br/><small>POST Communication Message</small>")]:::write
n1["Continue"]:::logic
end
subgraph g3["CRM Interaction"]
n14{{"Has Relationship?"}}:::decision
n15{{"Interaction Exists?"}}:::decision
n21[("Update Interaction<br/><small>PATCH CRM Interaction</small>")]:::write
n2[("Create Interaction<br/><small>POST CRM Interaction</small>")]:::write
end
n13 --> n0
n0 --> n17
n17 --> n6
n6 --> n20
n20 --> n8
n8 --> n7
n7 --> n4
n4 --> n11
n11 --> n19
n19 --> n10
n10 --> n9
n9 --> n5
n5 --> n12
n12 --> n18
n18 --> n16
n16 -->|true| n22
n16 -->|false| n3
n22 --> n1
n3 --> n1
n1 --> n14
n14 -->|true| n15
n15 -->|true| n21
n15 -->|false| n2
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:#111Systems it touches
| System | Table | Reads | Writes |
|---|---|---|---|
| Tooling and Access | Tool Account | Find Mailbox Account | Nothing |
| CRM | Membership (mirror) | Find Membership (CRM) | Nothing |
| CRM | People (mirror) | Find Person (CRM) | Nothing |
| CRM | Relationship | Find Relationships | Nothing |
| CRM | Interaction | Find Interaction | Create Interaction, Update Interaction |
| Communication | Membership (mirror) | Find Membership (Communication) | Nothing |
| Communication | Channel | Find Channel | Nothing |
| Communication | Person (mirror) | Find Person (Communication) | Nothing |
| Communication | Message | Find Message | Create Message, Update Message |
Every Airtable call is an HTTP Request node with one Airtable token credential. Reads filter by field name: {Login Alias} and {Tool} in Tool Account, {Membership #} in the CRM Membership mirror, {Membership ID} in the Communication Membership mirror, {External Identifier} in Channel, {Email} in both People mirrors, {External Message Id} in Message and {Evidence URL} in Interaction. Writes use field IDs. Renaming one of those fields breaks its lookup without an error: the node returns no record and the flow goes on as if nothing matched.
The CRM and Communication pages describe the records this workflow fills: Communication, CRM and Tooling and Access, where the mailbox’s Tool Account and its membership binding live.
Trigger and mailbox
Gmail Trigger polls the mailbox of its Gmail credential every minute with the search in:inbox OR in:sent, read and unread, and returns full messages (not the simplified format). Each new message is one item.
Config sets two values that every later node reads: mailbox, the address of this copy’s mailbox, and internalDomains, a comma-separated list of the company’s own e-mail domains. The mailbox in Config must be the same mailbox as the Gmail credential; nothing checks that they match.
What Normalize keeps
Normalize turns each message into one item and drops the ones that are not customer contact.
| Value | How it is set |
|---|---|
| Direction | Outbound when the sender is the mailbox or Gmail labels the message SENT; Inbound otherwise |
| Counterparty | Inbound: the sender. Outbound: the first address in To, then Cc, that is not on an internal domain. Other recipients are ignored |
| Skipped | No counterparty, a counterparty on an internal domain, or an address matching no-reply, noreply, mailer-daemon, notification or notifications |
| Gmail id and thread id | Gmail’s id and threadId; the thread id falls back to the message id |
| Subject | subject, cut to 250 characters; “(no subject)” when missing |
| Sent at | The message date; the processing time when the date cannot be read |
| Body | The plain-text part, or the Gmail snippet, cut to 20,000 characters |
| Snippet | The Gmail snippet, or the body, on one line and cut to 300 characters |
| Thread link | https://mail.google.com/mail/u/0/#all/ followed by the thread id |
When every message of a poll is skipped, the execution ends at Normalize.
Resolve the context
Each kept message runs nine lookups in a row, one HTTP call each.
- Mailbox account. Find Mailbox Account looks in Tooling for the Tool Account whose Login Alias is the mailbox and whose Tool contains Google Workspace. Resolve Seller reads its Bound Membership ID.
- Memberships. Find Membership (CRM) looks for the CRM Membership whose Membership # starts with that number followed by “ - “. Its Business Unit becomes the accountable company. Find Membership (Communication) looks for the Communication Membership with that Membership ID. With no Bound Membership ID, both lookups find nothing.
- Channel. Find Channel looks for the Communication Channel whose External Identifier is the mailbox.
- People. Find Person (CRM) looks for the counterparty’s e-mail in the CRM People mirror and Resolve Person collects the Relationships linked to it. Find Person (Communication) looks for the same e-mail in the Communication Person mirror.
- Existing records. Find Message looks for a Message whose External Message Id is the Gmail id. Find Interaction looks for an Interaction whose Evidence URL is the thread link.
- Relationship. Find Relationships reads the person’s Relationships. Prepare keeps the ones whose Stage* is not Lost or Disqualified and takes the first whose Accountable (Company)* contains the accountable company, then the first whose Accountable (Membership)* contains the membership, then the first open one.
Prepare then builds both records and adds a note when something is missing: no Tool Account for the mailbox, no membership, the counterparty not in the CRM People mirror, or no open Relationship. The notes go into the Message’s Failure Reason.
Write the Message
Message Exists? sends the item to Update Message when Find Message found one, and to Create Message otherwise. Both send the same fields, so processing a message again overwrites its Message with the same values. Continue then hands the prepared item to the CRM part.
The Message is written even when the person or the Relationship is missing; that is the only record such a message gets.
Write the Interaction
Has Relationship? stops the item unless Prepare found an open Relationship. Interaction Exists? then sends it to Update Interaction when an Interaction already has this thread link, and to Create Interaction otherwise. One thread is one Interaction: its Date* and Result follow the latest message processed.
| Message | Result | Next Contact Date° | Next Action° |
|---|---|---|---|
| Inbound, any | Answered | Today | “Reply to <name>: <subject>” |
| Outbound, first in the thread | No answer | Today plus three days | “Follow up if no reply: <subject>” |
| Outbound, later in the thread | No answer | Not changed | Not changed |
“Today” is the UTC date at processing time.
Fields written
Communication Message (Create Message and Update Message)
| Field | Value |
|---|---|
| Name | Subject |
| Body | Plain-text body, up to 20,000 characters |
| Sent At | Sent at |
| Status | Received for inbound, Sent for outbound |
| External Message Id | Gmail id |
| Direction | Inbound or Outbound |
| Thread Id | Gmail thread id |
| Context Base | CRM |
| About Keys | One line each: email: counterparty, mailbox: mailbox, gmail: Gmail id, and rfc: Message-ID when present |
| Context Key | Relationship/ followed by the Relationship record ID, when an open Relationship was found |
| Channel | The mailbox’s Channel, when found |
| Recipients (Person) | Outbound only: the counterparty in the Communication Person mirror, when found |
| Recipients (Membership) | Inbound only: the mailbox’s membership in the Communication Membership mirror, when found |
| Failure Reason | The notes from Prepare, when there are any |
CRM Interaction
| Field | Create Interaction | Update Interaction |
|---|---|---|
| Person (link to Relationship; deleted on 2026-11-04) | The open Relationship | Not changed |
| Date* | Sent at | Sent at of this message |
| Title* | Subject | Not changed |
| Type | Not changed | |
| Description° | “E-mail thread with <name> <e-mail> via <mailbox>. First message (<direction>): <snippet>” | Not changed |
| Result | Answered or No answer | Answered or No answer |
| Evidence URL | Thread link | Thread link |
| Next Contact Date° | See the table above | See the table above |
| Next Action° | See the table above | See the table above |
| Accountable (Membership) | The membership, when found | Not changed |
| Accountable (Company) | The membership’s Business Unit, when found | Not changed |
In the Interaction table, the field named Person was the link to Relationship; it was deleted with that table on 2026-11-04. Every write sends typecast: true: a select value that does not exist yet is created as a new option. Message Status has no Received option today, so the first inbound message creates it.
Nodes
The workflow has 24 nodes: 23 that run and one sticky note. For an If node, output 0 is true and output 1 is false.
| Node | Type | What it does |
|---|---|---|
| Gmail Trigger | gmailTrigger | Polls the mailbox every minute for inbox and sent mail |
| Config | set | Sets the mailbox address and the internal domains |
| Normalize | code | One item per e-mail; drops internal and no-reply mail |
| Find Mailbox Account | httpRequest | Tooling Tool Account of the mailbox |
| Resolve Seller | code | Reads Bound Membership ID and builds the membership lookups |
| Find Membership (CRM) | httpRequest | CRM Membership mirror by membership number |
| Find Membership (Communication) | httpRequest | Communication Membership mirror by Membership ID |
| Find Channel | httpRequest | Communication Channel of the mailbox |
| Find Person (CRM) | httpRequest | CRM People mirror by counterparty e-mail |
| Resolve Person | code | Builds the lookup of the person’s Relationships |
| Find Person (Communication) | httpRequest | Communication Person mirror by counterparty e-mail |
| Find Message | httpRequest | Message with the same External Message Id |
| Find Interaction | httpRequest | Interaction with the same thread link in Evidence URL |
| Find Relationships | httpRequest | The person’s CRM Relationships |
| Prepare | code | Picks the Relationship and builds the Message and Interaction bodies |
| Message Exists? | if | Update or create the Message |
| Update Message | httpRequest | PATCH the existing Message |
| Create Message | httpRequest | POST a new Message |
| Continue | code | Hands the prepared item on after the Message write |
| Has Relationship? | if | Stop unless an open Relationship was found |
| Interaction Exists? | if | Update or create the Interaction |
| Update Interaction | httpRequest | PATCH the thread Interaction |
| Create Interaction | httpRequest | POST the thread Interaction |
| Sticky Note | stickyNote | Setup note on the canvas; does not run |
Connections
| From | Output | To |
|---|---|---|
| Gmail Trigger | 0 | Config |
| Config | 0 | Normalize |
| Normalize | 0 | Find Mailbox Account |
| Find Mailbox Account | 0 | Resolve Seller |
| Resolve Seller | 0 | Find Membership (CRM) |
| Find Membership (CRM) | 0 | Find Membership (Communication) |
| Find Membership (Communication) | 0 | Find Channel |
| Find Channel | 0 | Find Person (CRM) |
| Find Person (CRM) | 0 | Resolve Person |
| Resolve Person | 0 | Find Person (Communication) |
| Find Person (Communication) | 0 | Find Message |
| Find Message | 0 | Find Interaction |
| Find Interaction | 0 | Find Relationships |
| Find Relationships | 0 | Prepare |
| Prepare | 0 | Message Exists? |
| Message Exists? | 0 | Update Message |
| Message Exists? | 1 | Create Message |
| Update Message | 0 | Continue |
| Create Message | 0 | Continue |
| Continue | 0 | Has Relationship? |
| Has Relationship? | 0 | Interaction Exists? |
| Interaction Exists? | 0 | Update Interaction |
| Interaction Exists? | 1 | Create Interaction |
Settings and error handling
| Setting | Value |
|---|---|
| Status | Inactive; never published |
| Execution order | v1 |
| Error workflow | None |
| Retry on fail | On every HTTP Request node, 3 tries |
| On error | Stop the workflow (default) on every node |
| Caller policy | Workflows from the same owner |
| Available in MCP | No |
| Execution data retention | Instance default; this workflow sets none |
The schedule is the Gmail Trigger poll, every minute. A call that still fails after three tries stops the execution; the items already written in that execution stay written.
Before activating
Check each item in the systems, then publish the workflow in n8n.
| Step | Where | State at the last review |
|---|---|---|
| The Gmail credential on Gmail Trigger belongs to the mailbox this copy logs | n8n | A credential is selected |
Config mailbox is that mailbox and internalDomains lists every company domain |
n8n | Filled |
| Find Relationships, Prepare and the Interaction writes use Deals instead of the deleted Relationship table and Person field | n8n | Pending; the nodes still read Relationship |
| The mailbox has a Tool Account with Login Alias = mailbox, Tool = Google Workspace and a Bound Membership ID | Tooling and Access | The Tool Account exists; Bound Membership ID is empty, so every Message would carry a “no active Identity Binding” note and Interactions would have no Accountable |
| A Channel with External Identifier = mailbox | Communication | Missing; Messages would have no Channel |
| Message Status has the option Received | Communication | Missing; the first inbound message creates it through typecast |
| A second mailbox gets its own copy, with its own credential and Config | n8n | One copy exists |
Known gaps
The workflow has never run. These come from the node configuration.
| Gap | Effect |
|---|---|
| Gmail thread ids are per mailbox | Two logged mailboxes on the same conversation make two Interactions |
The thread link always uses /u/0/ |
It opens the thread only when the mailbox is the first Google account signed in to the browser |
| Every outbound message sets Result to No answer | A reply you send after the customer answered turns an Answered Interaction back to No answer |
| The Relationship falls back to the first open one | With no company or membership match, the thread lands on any open Relationship of that person, even another seller’s |
| Only the first external recipient counts | A message to several customers is logged for one of them |
| “Today” and “plus three days” use the UTC date | Mail processed late in the evening in Brazil gets the next day |
| Failure Reason holds informational notes | A Message that was delivered fine shows text in a field named Failure Reason |
| The warning for a missing CRM person says “not a Person in Core” | The lookup is on the CRM People mirror; the person may exist in Core and not be mirrored yet |
| Find nodes read one page (up to 100 records) | A person with more than 100 Relationships can be matched to the wrong one |
| Up to eleven Airtable calls per message | A large first poll can hit Airtable’s rate limit; the three retries absorb short bursts only |
| The Message is never linked to its Interaction | Context Key holds the Relationship, not the Interaction |
Recover a partial write
Look at what was saved before you process a message again.
| What was saved | What to do |
|---|---|
| Message written, Interaction failed | Run it again; the Message is updated in place and the Interaction is created or updated by thread link |
| Message with “no open Relationship” | Log the Interaction by hand on the Deal; the Relationship table no longer exists |
| Message with a membership warning | Fill Bound Membership ID on the mailbox’s Tool Account, then set Accountable on the Interactions created meanwhile |
| Two Interactions for one thread | Keep one, keep its Evidence URL, then delete the other |
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 listed in Nodes and Connections.