Email Recipients ExecModule
Overview
EmailRecipientsModule is a local audience-selection step for email workflows. Version 2.0 replaces an undocumented config-only implementation that exposed no input schema and converted malformed data into an empty result. The module now validates a bounded recipient array, requires optIn: true, normalizes addresses, suppresses case-insensitive duplicates, removes unapproved fields, and returns explicit safe failures.
The module does not send email. Connect its recipients output to a separately configured provider module such as SendGrid, Gmail, or Microsoft Outlook only after the workflow has applied its required content, approval, frequency, and suppression policies.
Usage
- Add Email Recipients before the provider-specific email module.
- Provide recipients either as the static
recipientsconfiguration or as the runtimerecipientsinput. - Ensure every eligible recipient has a valid
emailand the literal Boolean valueoptIn: true. - Review the configured
maxRecipientsbound. - Map the returned
recipientsarray into the downstream delivery step. - Treat
excludedCountas a data-quality signal, not as delivery proof.
When a non-empty configured array is present, it takes precedence over runtime input. This makes the selected source deterministic and prevents an incoming workflow value from silently extending a reviewed static audience.
Inputs
| Input | Type | Required | Description | Constraints |
|---|---|---|---|---|
recipients | array | Conditional | Runtime recipient objects. Used only when static configuration is absent or empty. | At most maxRecipients; every entry must be an object. |
Each recipient object supports only these output fields:
| Field | Type | Required for eligibility | Constraint |
|---|---|---|---|
email | string | Yes | Valid address, maximum 254 characters; normalized to lowercase. Legacy Email is accepted as an input alias. |
optIn | boolean | Yes | Must be the literal value true. Missing, false, and non-Boolean values are excluded. |
firstName | string | No | Trimmed; maximum 128 characters; control characters rejected. |
lastName | string | No | Trimmed; maximum 128 characters; control characters rejected. |
locale | string | No | Trimmed; maximum 35 characters; control characters rejected. |
timeZone | string | No | Trimmed; maximum 64 characters; control characters rejected. |
Unknown fields are not emitted. This prevents unrelated profile data, tokens, internal notes, or provider configuration from flowing into a delivery module by accident.
Outputs
| Output | Type | Condition | Description |
|---|---|---|---|
status | string | Always | success or error. |
recipients | array | Success | Eligible unique recipients with allowlisted fields only. |
recipientCount | integer | Success | Number of recipients selected. |
excludedCount | integer | Success | Number excluded because consent was absent/false or the normalized address was a duplicate. |
error | object | Failure | Safe code, message, and retryable: false; it contains no recipient values. |
The output is classified as confidential because email addresses and names are personal data. The module logs aggregate counts and validation codes only.
IntegrationAccount Requirements
No IntegrationAccount is required or read. EmailRecipientsModule performs no provider authentication, network request, delivery, suppression lookup, or mailbox operation.
The downstream sender is responsible for using its own READY IntegrationAccount and for enforcing provider scopes, verified senders, account quotas, and delivery-specific approvals.
Configuration
| Configuration | Type | Default | Description | Constraint |
|---|---|---|---|---|
recipients | JSON array | [] | Reviewed static audience. A non-empty array takes precedence over runtime input. | Same object contract as the runtime input. |
maxRecipients | integer | 500 | Maximum number of source entries accepted in one execution. | 1–1,000. |
The bound applies before filtering and deduplication. A list with more entries than the reviewed maximum fails as a unit rather than returning a partial audience.
Operations
The module exposes one deterministic operation:
- Resolve static or runtime recipients.
- Require an array and enforce the configured entry bound.
- Exclude entries without explicit opt-in.
- Validate and normalize eligible email addresses and optional text fields.
- Remove non-allowlisted fields.
- Suppress duplicate lowercase addresses while preserving first-seen order.
- Return the eligible list and aggregate counts.
The module does not infer consent from presence in a list, a published status, an account role, or a prior delivery. It also does not synthesize opt-in for legacy records.
Errors and Failure Modes
| Code | Cause | Retryable | Recovery |
|---|---|---|---|
RECIPIENTS_REQUIRED | No static or runtime recipient exists. | No | Supply a non-empty recipient array. |
INVALID_RECIPIENTS | The top-level value is not an array or cannot be normalized safely. | No | Correct the JSON shape. |
INVALID_RECIPIENT | An eligible entry is not an object, has an invalid address, or contains an invalid supported text field. | No | Correct the indexed entry; the error omits its values. |
INVALID_LIMIT | maxRecipients is outside 1–1,000. | No | Choose a supported bound. |
LIMIT_EXCEEDED | The source array is larger than maxRecipients. | No | Reduce or explicitly partition the reviewed audience. |
NO_ELIGIBLE_RECIPIENTS | Every entry is opted out, lacks explicit opt-in, or is removed as a duplicate. | No | Reconcile consent records; do not override them in this module. |
Validation failures produce only status and error. A prior successful output is cleared before every execution, so stale recipients cannot survive a later failure.
Example
Configured input:
{
"recipients": [
{
"email": "Ada@Example.com",
"firstName": "Ada",
"locale": "en-US",
"optIn": true
},
{
"email": "grace@example.com",
"firstName": "Grace",
"optIn": false
},
{
"email": "ada@example.com",
"optIn": true
}
],
"maxRecipients": 100
}
Expected result:
{
"status": "success",
"recipients": [
{
"email": "ada@example.com",
"firstName": "Ada",
"locale": "en-US",
"optIn": true
}
],
"recipientCount": 1,
"excludedCount": 2
}
This result means one address is eligible for the next workflow step. It does not mean a message was sent, accepted, delivered, opened, or compliant with campaign-specific policy.
Notes
- Pagination: the module does not page an external audience. Partition large source datasets upstream under a reviewed workflow policy.
- Limits: one execution accepts at most 1,000 source entries and defaults to 500. Provider-specific recipient and message limits still apply later.
- Idempotency: selection is deterministic and side-effect free for the same configuration and input order.
- API constraints: there is no external API call, credential, retry, redirect, or rate-limit behavior.
- Destructive behavior: none. The module does not mutate a subscriber record, revoke consent, or send a message.
- Consent boundary:
optIn: trueis necessary for selection but may not be sufficient for a particular jurisdiction, purpose, brand, or campaign. Apply current legal and organizational policies before delivery. - Suppressions: unsubscribe, bounce, complaint, frequency-cap, and provider suppression lists must be enforced by an authorized upstream or delivery-stage service.
- Observability: logs contain counts and safe error codes only; recipient addresses and profile fields are not logged.
- IntegrationAccount: none. Live provider verification is not applicable to this local module.