<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.termatrac.net/index.php?action=history&amp;feed=atom&amp;title=So_many_Email_addresses</id>
	<title>So many Email addresses - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.termatrac.net/index.php?action=history&amp;feed=atom&amp;title=So_many_Email_addresses"/>
	<link rel="alternate" type="text/html" href="https://wiki.termatrac.net/index.php?title=So_many_Email_addresses&amp;action=history"/>
	<updated>2026-05-30T23:34:25Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.44.0</generator>
	<entry>
		<id>https://wiki.termatrac.net/index.php?title=So_many_Email_addresses&amp;diff=180&amp;oldid=prev</id>
		<title>Wikiadmin: Created page with &quot;(mostly answered by Claude.AI)  At the UI level, there is &#039;&#039;&#039;no direct Email ID field on the Customer DocType itself&#039;&#039;&#039;.  It &#039;&#039;&#039;is&#039;&#039;&#039; redundant by design, and it&#039;s a known architectural quirk in ERPNext. Here&#039;s why both exist and what each one actually does: ----  == The Two Fields Explained ==  === 1. &lt;code&gt;Contact.is_primary_contact&lt;/code&gt; (on the Contact DocType) ===  * This is a &#039;&#039;&#039;flag on the Contact record itself&#039;&#039;&#039; * It means: &#039;&#039;&quot;among all contacts linked to this...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.termatrac.net/index.php?title=So_many_Email_addresses&amp;diff=180&amp;oldid=prev"/>
		<updated>2026-05-22T06:25:45Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;(mostly answered by Claude.AI)  At the UI level, there is &amp;#039;&amp;#039;&amp;#039;no direct Email ID field on the Customer DocType itself&amp;#039;&amp;#039;&amp;#039;.  It &amp;#039;&amp;#039;&amp;#039;is&amp;#039;&amp;#039;&amp;#039; redundant by design, and it&amp;#039;s a known architectural quirk in ERPNext. Here&amp;#039;s why both exist and what each one actually does: ----  == The Two Fields Explained ==  === 1. &amp;lt;code&amp;gt;Contact.is_primary_contact&amp;lt;/code&amp;gt; (on the Contact DocType) ===  * This is a &amp;#039;&amp;#039;&amp;#039;flag on the Contact record itself&amp;#039;&amp;#039;&amp;#039; * It means: &amp;#039;&amp;#039;&amp;quot;among all contacts linked to this...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;(mostly answered by Claude.AI)&lt;br /&gt;
&lt;br /&gt;
At the UI level, there is &amp;#039;&amp;#039;&amp;#039;no direct Email ID field on the Customer DocType itself&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
It &amp;#039;&amp;#039;&amp;#039;is&amp;#039;&amp;#039;&amp;#039; redundant by design, and it&amp;#039;s a known architectural quirk in ERPNext. Here&amp;#039;s why both exist and what each one actually does:&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== The Two Fields Explained ==&lt;br /&gt;
&lt;br /&gt;
=== 1. &amp;lt;code&amp;gt;Contact.is_primary_contact&amp;lt;/code&amp;gt; (on the Contact DocType) ===&lt;br /&gt;
&lt;br /&gt;
* This is a &amp;#039;&amp;#039;&amp;#039;flag on the Contact record itself&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* It means: &amp;#039;&amp;#039;&amp;quot;among all contacts linked to this customer, this one is the primary&amp;quot;&amp;#039;&amp;#039;&lt;br /&gt;
* It is set when you tick &amp;#039;&amp;#039;&amp;#039;&amp;quot;Is Primary Contact&amp;quot;&amp;#039;&amp;#039;&amp;#039; on the Contact form&lt;br /&gt;
* ERPNext uses this to &amp;#039;&amp;#039;&amp;#039;auto-populate&amp;#039;&amp;#039;&amp;#039; contact fields in transactions (Sales Order, Sales Invoice, etc.)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Problem:&amp;#039;&amp;#039;&amp;#039; Nothing enforces uniqueness — two contacts for the same customer can both have &amp;lt;code&amp;gt;is_primary_contact = 1&amp;lt;/code&amp;gt;, which is a data integrity gap&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== 2. &amp;lt;code&amp;gt;Customer.customer_primary_contact&amp;lt;/code&amp;gt; (on the Customer DocType) ===&lt;br /&gt;
&lt;br /&gt;
* This is a &amp;#039;&amp;#039;&amp;#039;direct link field&amp;#039;&amp;#039;&amp;#039; (a &amp;lt;code&amp;gt;Link&amp;lt;/code&amp;gt; field pointing to &amp;lt;code&amp;gt;Contact&amp;lt;/code&amp;gt;) stored on the Customer record itself&lt;br /&gt;
* It gives you a &amp;#039;&amp;#039;&amp;#039;single, authoritative lookup&amp;#039;&amp;#039;&amp;#039;: &amp;#039;&amp;#039;&amp;quot;what is the primary contact for this customer?&amp;quot;&amp;#039;&amp;#039; without having to query the Contact table at all&lt;br /&gt;
* It is updated automatically by ERPNext when a contact is marked as primary&lt;br /&gt;
* Much faster for API queries — one GET on the Customer record returns the primary contact name immediately&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Why Both Exist — The Real Reason ==&lt;br /&gt;
 &amp;lt;code&amp;gt;Customer ──────────────────────────────────────────────────────────┐&lt;br /&gt;
   customer_primary_contact = &amp;quot;Jane Smith&amp;quot;  ← fast denormalised     │&lt;br /&gt;
                                               lookup                │&lt;br /&gt;
                                                                     │&lt;br /&gt;
 Contact (Jane Smith)                                                │&lt;br /&gt;
   links → [{ link_doctype: &amp;quot;Customer&amp;quot;, link_name: &amp;quot;CUST-00001&amp;quot; }]  │&lt;br /&gt;
   is_primary_contact = 1  ← flag used during transaction           │&lt;br /&gt;
                              auto-population &amp;amp; filtering ───────────┘&amp;lt;/code&amp;gt;&lt;br /&gt;
It is essentially &amp;#039;&amp;#039;&amp;#039;denormalisation for performance&amp;#039;&amp;#039;&amp;#039; — a classic database trade-off:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Concern&lt;br /&gt;
!&amp;lt;code&amp;gt;is_primary_contact&amp;lt;/code&amp;gt;&lt;br /&gt;
!&amp;lt;code&amp;gt;customer_primary_contact&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;#039;&amp;#039;&amp;#039;Where stored&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
|Contact record&lt;br /&gt;
|Customer record&lt;br /&gt;
|-&lt;br /&gt;
|&amp;#039;&amp;#039;&amp;#039;Purpose&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
|Flag for filtering/transactions&lt;br /&gt;
|Fast direct lookup&lt;br /&gt;
|-&lt;br /&gt;
|&amp;#039;&amp;#039;&amp;#039;Query cost&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
|Requires JOIN / filter on Contact&lt;br /&gt;
|Single field on Customer fetch&lt;br /&gt;
|-&lt;br /&gt;
|&amp;#039;&amp;#039;&amp;#039;Risk&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
|Multiple contacts can be flagged &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
|Can go stale if not synced&lt;br /&gt;
|-&lt;br /&gt;
|&amp;#039;&amp;#039;&amp;#039;Updated by&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
|User manually / Contact save hook&lt;br /&gt;
|ERPNext hook on Contact save&lt;br /&gt;
|}&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== The Sync Risk ==&lt;br /&gt;
The real danger is they can &amp;#039;&amp;#039;&amp;#039;fall out of sync&amp;#039;&amp;#039;&amp;#039;:&lt;br /&gt;
&lt;br /&gt;
* If you update &amp;lt;code&amp;gt;is_primary_contact&amp;lt;/code&amp;gt; via a direct API PATCH on the Contact but ERPNext&amp;#039;s server-side hook doesn&amp;#039;t fire correctly, &amp;lt;code&amp;gt;customer_primary_contact&amp;lt;/code&amp;gt; on the Customer won&amp;#039;t update&lt;br /&gt;
* If you delete a Contact that is the primary, &amp;lt;code&amp;gt;customer_primary_contact&amp;lt;/code&amp;gt; may hold a &amp;#039;&amp;#039;&amp;#039;dead reference&amp;#039;&amp;#039;&amp;#039; until another contact is promoted&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Practical Advice ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Use case&lt;br /&gt;
!Use this field&lt;br /&gt;
|-&lt;br /&gt;
|Fetch primary contact quickly in one API call&lt;br /&gt;
|&amp;lt;code&amp;gt;Customer.customer_primary_contact&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|Filter all contacts to find which is primary&lt;br /&gt;
|&amp;lt;code&amp;gt;Contact.is_primary_contact = 1&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|Trust for data integrity&lt;br /&gt;
|Neither alone — &amp;#039;&amp;#039;&amp;#039;verify both match&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
|-&lt;br /&gt;
|Setting a new primary contact&lt;br /&gt;
|Always do it via the &amp;#039;&amp;#039;&amp;#039;ERPNext UI or server-side method&amp;#039;&amp;#039;&amp;#039; so both fields stay in sync&lt;br /&gt;
|}&lt;br /&gt;
---- It&amp;#039;s a deliberate denormalisation that trades data integrity risk for query performance, and it&amp;#039;s something ERPNext developers themselves have debated in the Frappe GitHub issues over the years.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
ERPNext&amp;#039;s &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; DocType also has its own &amp;lt;code&amp;gt;email_id&amp;lt;/code&amp;gt; field, completely separate from the Contact email chain.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== The Full Picture ==&lt;br /&gt;
 &amp;lt;code&amp;gt;Customer (CUST-00001 - &amp;quot;Acme Corp&amp;quot;)&lt;br /&gt;
   │&lt;br /&gt;
   ├──► customer_primary_contact ──► Contact &amp;quot;Jane Smith&amp;quot;&lt;br /&gt;
   │         │                           └── email_ids[]&lt;br /&gt;
   │         │                                 └── jane@acme.com  ← person email&lt;br /&gt;
   │&lt;br /&gt;
   ├──► customer_primary_address ──► Address &amp;quot;Acme Corp-Billing&amp;quot;&lt;br /&gt;
   │                                     └── email_id: accounts@acme.com  ← address email&lt;br /&gt;
   │&lt;br /&gt;
   └──► Dynamic Links (multiple)&lt;br /&gt;
             │&lt;br /&gt;
             ├── Address &amp;quot;Acme Corp-Billing&amp;quot;&lt;br /&gt;
             │     └── email_id: accounts@acme.com&lt;br /&gt;
             │&lt;br /&gt;
             ├── Address &amp;quot;Acme Corp-Shipping&amp;quot;&lt;br /&gt;
             │     └── email_id: warehouse@acme.com&lt;br /&gt;
             │&lt;br /&gt;
             └── Address &amp;quot;Acme Corp-Head Office&amp;quot;&lt;br /&gt;
                   └── email_id: info@acme.com&amp;lt;/code&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Three Completely Separate Email Pools ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Source&lt;br /&gt;
!Field&lt;br /&gt;
!Typical Use&lt;br /&gt;
!Example&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Contact.email_ids[]&amp;lt;/code&amp;gt;&lt;br /&gt;
|&amp;lt;code&amp;gt;email_id&amp;lt;/code&amp;gt;&lt;br /&gt;
|Person-to-person communication&lt;br /&gt;
|&amp;lt;code&amp;gt;jane@acme.com&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Address.email_id&amp;lt;/code&amp;gt;&lt;br /&gt;
|&amp;lt;code&amp;gt;email_id&amp;lt;/code&amp;gt;&lt;br /&gt;
|Location/department email&lt;br /&gt;
|&amp;lt;code&amp;gt;accounts@acme.com&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Customer&amp;lt;/code&amp;gt;&lt;br /&gt;
|&amp;#039;&amp;#039;(none)&amp;#039;&amp;#039;&lt;br /&gt;
|—&lt;br /&gt;
|—&lt;br /&gt;
|}&lt;br /&gt;
Each pool is &amp;#039;&amp;#039;&amp;#039;entirely independent&amp;#039;&amp;#039;&amp;#039; — no sync, no relationship between them whatsoever.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Why Address Has Its Own Email ==&lt;br /&gt;
The &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; DocType is designed to represent a &amp;#039;&amp;#039;&amp;#039;physical or operational location&amp;#039;&amp;#039;&amp;#039;, and in B2B that location often has its own functional email:&lt;br /&gt;
 &amp;lt;code&amp;gt;Acme Corp - Billing Address  → accounts@acme.com   (AP/AR team)&lt;br /&gt;
 Acme Corp - Shipping Address → warehouse@acme.com  (logistics team)&lt;br /&gt;
 Acme Corp - Head Office      → info@acme.com       (general enquiries)&amp;lt;/code&amp;gt;&lt;br /&gt;
This makes sense in the real world — when you send an &amp;#039;&amp;#039;&amp;#039;invoice&amp;#039;&amp;#039;&amp;#039;, you email the billing address email, not Jane&amp;#039;s personal email.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== How ERPNext Decides Which Email to Use ==&lt;br /&gt;
This is where it gets messy. Depending on the transaction type, ERPNext picks email from &amp;#039;&amp;#039;&amp;#039;different sources&amp;#039;&amp;#039;&amp;#039;:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Transaction&lt;br /&gt;
!Where ERPNext looks for email&lt;br /&gt;
|-&lt;br /&gt;
|Sales Invoice (send via email)&lt;br /&gt;
|&amp;lt;code&amp;gt;customer_primary_contact → email_id&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|Print &amp;amp; Email from Address&lt;br /&gt;
|&amp;lt;code&amp;gt;Address.email_id&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|Dunning / Payment Reminder&lt;br /&gt;
|&amp;lt;code&amp;gt;customer_primary_contact → email_id&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|Shipping Notification&lt;br /&gt;
|&amp;lt;code&amp;gt;Address.email_id&amp;lt;/code&amp;gt; (shipping address)&lt;br /&gt;
|-&lt;br /&gt;
|Portal / User login&lt;br /&gt;
|&amp;lt;code&amp;gt;Contact.email_ids[]&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
There is &amp;#039;&amp;#039;&amp;#039;no single unified &amp;quot;customer email&amp;quot;&amp;#039;&amp;#039;&amp;#039; — it depends entirely on the context and which button you press.&lt;br /&gt;
----&lt;br /&gt;
 &lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Summary of the Design Problem ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Issue&lt;br /&gt;
!Impact&lt;br /&gt;
|-&lt;br /&gt;
|No single &amp;lt;code&amp;gt;Customer.email&amp;lt;/code&amp;gt; field&lt;br /&gt;
|Must chase 2+ DocTypes to find any email&lt;br /&gt;
|-&lt;br /&gt;
|Address email ≠ Contact email&lt;br /&gt;
|Easy to send invoices to the wrong place&lt;br /&gt;
|-&lt;br /&gt;
|No enforced &amp;quot;use this email for invoices&amp;quot; rule&lt;br /&gt;
|Relies on user discipline&lt;br /&gt;
|-&lt;br /&gt;
|Multiple addresses, each with own email&lt;br /&gt;
|No clear hierarchy without &amp;lt;code&amp;gt;is_primary_address&amp;lt;/code&amp;gt; logic&lt;br /&gt;
|-&lt;br /&gt;
|ERPNext picks email source based on transaction&lt;br /&gt;
|Inconsistent and surprising behaviour&lt;br /&gt;
|}&lt;br /&gt;
It is a &amp;#039;&amp;#039;&amp;#039;flexible but fragile&amp;#039;&amp;#039;&amp;#039; design — powerful when set up correctly, but very easy to end up with missing or stale email data across the system.&lt;/div&gt;</summary>
		<author><name>Wikiadmin</name></author>
	</entry>
</feed>