# Communication — Record Email

Sub-workflow: writes one captured e-mail as a Message in Communication and its thread as an Interaction in the CRM.

| Platform | Workflow ID | Status | Version | Last saved | Nodes | Systems |
| --- | --- | --- | --- | --- | --- | --- |
| n8n | HFJxURe214QwOkMM | Inactive | 4388d0fc-19a2-4e8d-bd47-ab75803f3166 | 2026-11-06T21:25:38.694Z | 19 | Communication, CRM |

Communication — Record Email writes one captured e-mail into the systems. It creates or updates one Message in Communication, keyed by the e-mail's Message-ID, and one Interaction in the CRM per thread, keyed by the thread link. It links the Message to the people who wrote and received it and the Interaction to the person, their company, the open Deal and the seller's Membership, and it moves the follow-up date: reply today when the customer wrote, check again in three days after the first message the seller sent. Both records carry the tag `email-log-n8n`. It never creates a person or a Deal.

It is a sub-workflow: it runs when [Email Capture](/automations/email-capture) calls it with a normalized e-mail and the context from [CRM — Resolve Context](/automations/crm-resolve-context).

The Communication maintainer owns the Message records; 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
flowchart TD
  subgraph g0["Request and lookups"]
    n13(["Record Request"]):::trigger
    n1["Config"]:::logic
    n0["Build Lookups"]:::logic
    n9[("Find Message<br/><small>GET Communication Message</small>")]:::read
    n7[("Find Interaction<br/><small>GET CRM Interaction</small>")]:::read
    n6[("Find External People<br/><small>GET Communication Person</small>")]:::read
    n5[("Find Accounts<br/><small>GET Communication Tool Account</small>")]:::read
    n14["Resolve Accounts"]:::logic
    n8[("Find Internal People<br/><small>GET Communication Membership</small>")]:::read
    n12["Prepare"]:::logic
  end
  subgraph g1["Communication Message"]
    n11{{"Message Exists?"}}:::decision
    n17[("Update Message<br/><small>PATCH Communication Message</small>")]:::write
    n4[("Create Message<br/><small>POST Communication Message</small>")]:::write
    n2["Continue"]:::logic
  end
  subgraph g2["CRM Interaction"]
    n10{{"Interaction Exists?"}}:::decision
    n16[("Update Interaction<br/><small>PATCH CRM Interaction</small>")]:::write
    n3[("Create Interaction<br/><small>POST CRM Interaction</small>")]:::write
    n15["Result"]:::logic
  end
  n13 --> n1
  n1 --> n0
  n0 --> n9
  n9 --> n7
  n7 --> n6
  n6 --> n5
  n5 --> n14
  n14 --> n8
  n8 --> n12
  n12 --> n11
  n11 -->|true| n17
  n11 -->|false| n4
  n17 --> n2
  n4 --> n2
  n2 --> n10
  n10 -->|true| n16
  n10 -->|false| n3
  n16 --> n15
  n3 --> n15
  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

| System | Table | Reads | Writes |
| --- | --- | --- | --- |
| Communication | Message | Find Message | Create Message, Update Message |
| Communication | Person (mirror) | Find External People | Nothing |
| Communication | Tool Account (mirror) | Find Accounts | Nothing |
| Communication | Membership (mirror) | Find Internal People | Nothing |
| CRM | Interaction | Find Interaction | Create Interaction, Update Interaction |

Every Airtable call is an HTTP Request node with an Airtable token credential. The reads filter by field name: `{External Message Id}` on Message, `{Evidence URL}` on Interaction, `{Email}` on the Person mirror `{Login Alias}` and `{Tool}` on the Tool Account mirror, and `{Membership ID}` and `{Company Email}` on the Membership mirror. The writes use field ids. Renaming one of the read fields breaks the lookup without an error: the node returns no record and the flow creates a duplicate.

## Input and configuration

Record Request receives two objects from the caller: `message`, the normalized e-mail (direction, seller, counterparty, external Cc, internal participants, Message-ID, thread id and link, subject, body, snippet, sent at), and `context`, the CRM context (membership, accountable company, person, person company, Deal, notes).

Config holds the record ids the writes need: `channelId` (the Communication Channel of the capture mailbox), and `tagCommunicationId` and `tagCrmId` (the tag `email-log-n8n` in each base).

## Lookups

Build Lookups builds three formulas. The Message key is the RFC Message-ID, or the Gmail id when the header is missing. Find Message looks, in one call, for the Message with that External Message Id and for the Message whose External Message Id is the e-mail's In-Reply-To header, when the header exists. Find Interaction looks for an Interaction whose Evidence URL is the thread link. Find External People looks up, in one call, the counterparty and every external Cc address in the Communication Person mirror. Find Accounts looks up, in one call, the seller and every internal participant in the Tool Account mirror, by Login Alias among the Google Workspace accounts. Resolve Accounts reads the Bound Membership ID of each account found, and Find Internal People reads those Memberships in the Membership mirror, by Membership ID or by Company Email, to reach the Person behind each internal address.

## Write the Message

Message Exists? sends the item to Update Message when Find Message found one, and to Create Message otherwise. Continue then hands the prepared item on to the CRM half.

A second capture of the same Message-ID happens when another internal account was in copy: Google delivers one copy per internal recipient. In that case Update Message changes only About Keys, merging the new `account:` and `participant:` lines into the existing ones, and Participants (Account), adding the accounts of the new copy. Nothing else on the Message is touched.

## Write the Interaction

Interaction Exists? sends the item 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 last message processed.

| Message | Result | Next Contact Date° | Next Action° |
| --- | --- | --- | --- |
| Inbound, any | Answered | Today | "Reply to &lt;name&gt;: &lt;subject&gt;" |
| Outbound, first in the thread | No answer | Today plus three days | "Follow up if no reply: &lt;subject&gt;" |
| Outbound, later in the thread | No answer | Unchanged | Unchanged |

"Today" is the UTC date at processing time. Result reports what was written: the Message id, the Interaction id and whether each was updated or created.

## Fields written

### Communication Message (Create Message)

| Field | Value |
| --- | --- |
| Subject | Subject (Name is a formula: who wrote to whom, then the Subject) |
| Body | Plain text, up to 20,000 characters |
| Sent At | Sent at |
| Status | Received for inbound, Sent for outbound |
| External Message Id | RFC Message-ID, or the Gmail id when missing |
| Direction | Inbound or Outbound |
| Thread Id | Gmail thread id in the capture mailbox |
| Context Base | CRM |
| Context Key | `Deal/` plus the Deal record id, or `Person/` plus the person record id |
| Channel | The Channel in Config |
| Tags | email-log-n8n |
| Sender (Person) | Who wrote it: the counterparty on Inbound, the seller's Person on Outbound |
| Receivers (Person) | Everyone who received it and exists as a Person: Outbound, the counterparty, the external Cc and the internal colleagues in copy; Inbound, the seller, the external Cc and the internal colleagues in copy |
| Account | The seller's record in the Tool Account mirror, by Login Alias, when found |
| In Reply To | The captured Message the e-mail answers, by its In-Reply-To header; empty when that e-mail was not captured. Replies on the parent is the inverse |
| Participants (Account) | The other internal addresses in To or Cc that have a Tool Account |
| About Keys | One line each: `email:` counterparty, `account:` seller, `participant:` each other internal address, `cc:` each external Cc not in the Person mirror, `gmail:` id, `rfc:` Message-ID, `in-reply-to:` when present |

Update Message writes About Keys and Participants (Account) only, as described above.

### CRM Interaction

| Field | Create Interaction | Update Interaction |
| --- | --- | --- |
| People° | The person from the context | Unchanged |
| Company° | The person's company from the context, when any | Unchanged |
| Deal° | The open Deal from the context, when any | Unchanged |
| Date* | Sent at | Sent at of this message |
| Title* | Subject | Unchanged |
| Type | Email | Unchanged |
| Result | Answered or No answer | Answered or No answer |
| Description° | "E-mail thread with &lt;name&gt; &lt;e-mail&gt; via &lt;seller mailbox&gt;. First message (&lt;direction&gt;): &lt;snippet&gt;" | Unchanged |
| Note | The context notes, a seller without a Person behind the account, and the external Cc not in People, when any | Unchanged |
| Evidence URL | Thread link in the capture mailbox | Thread link |
| Next Contact Date° | See the table above | See the table above |
| Next Action° | See the table above | See the table above |
| Accountable (Person) | The seller as a CRM Person (Member of the Membership found), when found | Unchanged |
| Accountable (Company) | The Membership's Business Unit, when found | Unchanged |
| Tags | email-log-n8n | Unchanged |

Every write sends `typecast: true`: a select value that does not exist yet is created as a new option.

## Nodes

The workflow has 19 nodes: 18 that run and one sticky note. For an If node, output 0 is true and output 1 is false.

| Node | Type | What it does |
| --- | --- | --- |
| Record Request | executeWorkflowTrigger | Receives message and context from the caller |
| Config | set | Channel id, the two tag ids and the optional Account field |
| Build Lookups | code | Message key, thread link and external e-mails as formulas |
| Find Message | httpRequest | Message with the same External Message Id, or with the In-Reply-To id |
| Find Interaction | httpRequest | Interaction with the same thread link in Evidence URL |
| Find External People | httpRequest | Communication Person mirror by the external e-mails |
| Find Accounts | httpRequest | Communication Tool Account mirror by the internal e-mails |
| Resolve Accounts | code | Reads the accounts and builds the Membership lookup by Bound Membership ID or Company Email |
| Find Internal People | httpRequest | Communication Membership mirror: the people behind the internal accounts |
| Prepare | code | Builds the Message and Interaction bodies and the follow-up values |
| Message Exists? | if | Update or create the Message |
| Update Message | httpRequest | PATCH About Keys of the existing Message |
| Create Message | httpRequest | POST a new Message |
| Continue | code | Hands the prepared item on after the Message write |
| Interaction Exists? | if | Update or create the Interaction |
| Update Interaction | httpRequest | PATCH the thread Interaction |
| Create Interaction | httpRequest | POST the thread Interaction |
| Result | code | Returns the Message and Interaction ids to the caller |
| Sticky Note | stickyNote | Setup note on the canvas; does not run |

## Connections

| From | Output | To |
| --- | --- | --- |
| Record Request | 0 | Config |
| Config | 0 | Build Lookups |
| Build Lookups | 0 | Find Message |
| Find Message | 0 | Find Interaction |
| Find Interaction | 0 | Find External People |
| Find External People | 0 | Find Accounts |
| Find Accounts | 0 | Resolve Accounts |
| Resolve Accounts | 0 | Find Internal People |
| Find Internal People | 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 | Interaction Exists? |
| Interaction Exists? | 0 | Update Interaction |
| Interaction Exists? | 1 | Create Interaction |
| Update Interaction | 0 | Result |
| Create Interaction | 0 | Result |

## Settings and error handling

| Setting | Value |
| --- | --- |
| Status | Inactive; a sub-workflow runs when called even while inactive |
| 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; the caller receives the error and labels the copy CRM/CRM - Error |
| 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 Channel of `non-reply@` in Config exists and is Active | Communication | Exists, Draft |
| The tag `email-log-n8n` exists in Core and reached both mirrors | Core, CRM, Communication | Done |
| The Message Status has the option Received | Communication | Done |
| The Tool Account mirror exists and every seller mailbox is in it with its Login Alias | Communication, Tooling and Access | Done on 2026-11-06; an address without a Tool Account stays only in About Keys |

## Known gaps

| Gap | Effect |
| --- | --- |
| Every outbound message sets Result to No answer | A reply sent after the customer answered turns an Answered Interaction back to No answer |
| Only About Keys and Participants (Account) are merged on a repeated Message-ID | A copy that arrives with more external Cc than the first one does not add Receivers (Person) |
| A thread with no open Deal at first capture never gains the Deal | Deal° is set only on Create Interaction |
| The Message is never linked to its Interaction | Context Key holds the Deal or the person, not the Interaction |
| "Today" and "plus three days" use the UTC date | E-mail processed late at night in Brazil gets the next day |

## Recover a partial write

| What was saved | What to do |
| --- | --- |
| Message written, Interaction failed | Run the e-mail again; the Message is updated in place and the Interaction is created or updated by thread link |
| Two Interactions for one thread | Keep one, keep its Evidence URL, then delete the other |
| Message with a wrong person | Fix the links by hand; the next capture of the thread does not touch them |

## 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).