---
title: Email info CRUD and linking
summary: Persists standalone EMAIL_ADDRESS rows, applies configurable syntax/DNS validation, and maintains the customer/contact link rows that reference them.
status: draft
owner: SAIF
last_reviewed: 2026-07-30
library: SAIF.Business.CustomerContact
reference_type: package
operation_id: OP-CC-06
trigger: Caller adds/edits a clsEmailAddressInfo in a customer's or contact's Emails collection and the parent saves
---

# Email info CRUD and linking

`clsEmailAddressInfo` is a standalone `EMAIL_ADDRESS` row with a configurable validation model: callers choose both how strict the check is (syntax only vs. syntax-and-DNS) and how a failure is treated (warning vs. error). As with addresses, the owning collection tracks adds/removes and reconciles the `COMPANY_EMAIL` / `PERSON_EMAIL` link rows on save.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-CC-06 |
| **Trigger** | Caller adds/removes a `clsEmailAddressInfo` in `clsCustomer.Emails` or `clsContact.Emails`, then the parent's `Save()` cascades into the collection; or `mdlCustomerContact.CreateEmail` is called directly |
| **Inputs** | `Value` (email address text), `EmailCode` (email type), `IsPrimary`, `EmailAddressValidation` (`Warn`/`Error`), `EmailValidationType` (`None`/`Syntax`/`SyntaxAndDNS`) |
| **Outputs** | Persisted `EMAIL_ADDRESS` row; persisted or removed `COMPANY_EMAIL` / `PERSON_EMAIL` link row; `IsVerified` flag reset on value change |
| **Primary components** | `clsEmailAddressInfo.Create`, `clsEmailAddressInfo.OnValueChange`, `clsEmailAddressInfo.AddBusinessRules`, `mdlCustomerContact.CreateEmail`, `clsInfoCollection.Save` |
| **Entry point** | `Business\SAIF.Business.CustomerContact\InfoItems\clsEmailAddressInfo.vb` |

## Steps

1. **Create a new email row.** `Create()` assigns the row's `EmailAddressID` from `GetNextSeqNum`.
2. **Value-change side effect** ([BR-010](../business-rules.md#br-010-changing-an-email-address-value-clears-its-verified-flag)). `OnValueChange` calls the base handler, then unconditionally sets `IsVerified = False` — any edit to the email text invalidates prior verification.
3. **Configure the validation model** ([BR-006](../business-rules.md#br-006-email-validation-severity-and-depth-are-configurable)). Setting `EmailAddressValidation` (`Warn` or `Error`) rebuilds the rule set via `AddBusinessRules()`, which clears existing rules and registers:
   - Required check: `ValidateEmailRequiredError` (level one) if `EmailAddressValidation = Error`, or `ValidateEmailRequiredWarning` if `Warn`.
   - Syntax check (level two, only if `EmailValidationType = Syntax`): `ValidateEmailSyntaxError`/`ValidateEmailSyntaxWarning`, backed by `clsEmailAddress.IsValidEmailAddressSyntax(...)`.
   - Syntax-and-DNS check (level two, only if `EmailValidationType = SyntaxAndDNS`): `ValidateEmailSyntaxAndDNSError`/`ValidateEmailSyntaxAndDNSWarning`, backed by `clsEmailAddress.IsValidEmailAddress(...)`.
4. **Persist the root `EMAIL_ADDRESS` row.** Insert ([SQL-014](../sql/SQL-014.md)/[SQL-015](../sql/SQL-015.md)), update ([SQL-071](../sql/SQL-071.md)/[SQL-072](../sql/SQL-072.md)), and delete ([SQL-088](../sql/SQL-088.md)/[SQL-089](../sql/SQL-089.md)) against [EMAIL_ADDRESS](../database-tables.md#email-address).
5. **Reconcile link rows** through `clsInfoCollection.Save()`, in the same add/remove-tracking pattern used for addresses (see [Address info CRUD and linking](address-info-crud-and-linking.md)): save current email rows, delete removed link rows (customer: [SQL-085](../sql/SQL-085.md) against [COMPANY_EMAIL](../database-tables.md#company-email); contact: [SQL-079](../sql/SQL-079.md) against [PERSON_EMAIL](../database-tables.md#person-email)), then insert added link rows (customer: [SQL-011](../sql/SQL-011.md); contact: [SQL-005](../sql/SQL-005.md)).
6. **Helper factory flow.** `mdlCustomerContact.CreateEmail(dpi, contact, address, isPrimary, emailType)`:
   - Creates `clsEmailAddressInfo.NewEmailAddress(dpi)`.
   - Forces `EmailAddressValidation = ValidationLevel.Error` regardless of caller preference.
   - Sets `IsPrimary` and `Value`.
   - Resolves `EmailCode` from `mdlCodeMaps.GetEmailTypes(dpi)(emailType).Code`.
   - Adds the new email directly to `contact.Emails` **unconditionally** — unlike the phone helper (`CreatePhone`), it does not gate the add on `IsValid`.
7. **Resolve the email-type description.** `EmailTypeDescription` calls `GetEmailCode(dpi)(EmailCode).Description` from the shared `SAIF.Business` code-map module for display purposes only.

## Process flow

The following diagram visualizes email validation configuration and the save/link cycle.

```mermaid
flowchart TD
	Start([Caller sets Value on clsEmailAddressInfo]) --> Reset[OnValueChange:<br>IsVerified = False]
	Reset --> Config[EmailAddressValidation set:<br>rebuild rules via AddBusinessRules]
	Config --> Level{ValidationLevel?}
	Level -->|Error| ReqError[Register required-error rule]
	Level -->|Warn| ReqWarn[Register required-warning rule]
	ReqError --> TypeCheck{EmailValidationType?}
	ReqWarn --> TypeCheck
	TypeCheck -->|Syntax| SyntaxRule[Register syntax rule]
	TypeCheck -->|SyntaxAndDNS| DnsRule[Register syntax+DNS rule]
	TypeCheck -->|None| NoRule[No format rule registered]
	SyntaxRule --> ParentSave
	DnsRule --> ParentSave
	NoRule --> ParentSave
	ParentSave([Parent Save cascades to collection]) --> SaveRows[Save each current EMAIL_ADDRESS row]
	SaveRows --> DeleteLinks[Delete link rows for RemovedItems]
	DeleteLinks --> InsertLinks[Insert link rows for AddedItems]
	InsertLinks --> Done([Collection persisted])
```

## Failure and edge-case behavior
- With `EmailAddressValidation = Error` and a required/syntax/DNS failure, the save is blocked by a broken rule; with `Warn`, the same conditions produce a warning only and do not block the save ([BR-006](../business-rules.md#br-006-email-validation-severity-and-depth-are-configurable)).
- Any edit to the email `Value` clears `IsVerified`, even if the new value is identical in meaning to the old one (for example, only case differs) — verification is not re-derived automatically ([BR-010](../business-rules.md#br-010-changing-an-email-address-value-clears-its-verified-flag)).
- `mdlCustomerContact.CreateEmail` adds the new email to the contact's collection before any validation gate is checked, so an invalid address can be added and only surface as a broken rule later, at save time — unlike `CreatePhone`, which checks `IsValid` before adding.*
- Removing an email from a collection deletes only the `COMPANY_EMAIL` / `PERSON_EMAIL` link row, not the standalone `EMAIL_ADDRESS` row.*

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts.

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [SQL catalog](../sql/index.md)
- [Database tables](../database-tables.md)

## Sources
- `Business\SAIF.Business.CustomerContact\InfoItems\clsEmailAddressInfo.vb`
- `Business\SAIF.Business.CustomerContact\InfoItems\clsInfoCollection.vb`
- `Business\SAIF.Business.CustomerContact\Customer\clsCustomerEmailCollection.vb`
- `Business\SAIF.Business.CustomerContact\Contact\clsContactEmailCollection.vb`
- `Business\SAIF.Business.CustomerContact\mdlCustomerContact.vb`
