Contact Policies are post-qualification rules that remove Offers a customer is eligible for but should not receive right now. The decision-flow engine evaluates them after qualification rules and consent checks, but before scoring and ranking. Each policy inspects materialized interaction summaries (impression counts, last-contact timestamps, outcome history) and either blocks or allows a candidate.Policies are evaluated in priority order (highest first). An allow_override policy that matches a candidate causes the engine to skip all blocking rules for that candidate. Otherwise, the first blocking rule that fires removes the candidate from the result set.Mandatory Offers (marked isMandatory) still respect frequency_cap and cross_channel_cap rules plus any policy where config.bypassable is false. All other blocking rules are skipped for mandatory Offers.
KaireonAI supports 10 rule types. Nine are defined across the validation schema and domain rules; one (metric_condition) lives in the engine and domain layer but is not yet exposed in the API validation enum.
Rule Type
Summary
Scopes
frequency_cap
Max impressions per day / week / month / total
global, offer, creative, channel
cooldown
Minimum hours between contacts
global, offer, creative, channel
budget_exhausted
Suppress when impression or spend budget consumed
offer, creative
outcome_based
Suppress after a specific outcome (e.g. complaint)
offer, creative
segment_exclusion
Exclude customers in named segments
global
time_window
Restrict to specific hours and days of the week
global, channel
mutual_exclusion
If one Offer in a group was served, suppress the others
offer
cross_channel_cap
Frequency cap aggregated across all channels
global, offer, creative, channel
allow_override
Explicitly allow contact despite other blocking rules
global, offer, creative, channel
category_suppression
Suppress all Offers in a category after any was shown recently
global
metric_condition
Block when a behavioral metric crosses a threshold
global, offer, creative, channel
metric_condition is implemented in the contact-policy engine but is not included in the API validation enum. All other ten types (including category_suppression) are fully wired end-to-end.
Blocks all Offers for customers belonging to one or more excluded segments. This is a global-only rule.Config fields:
Field
Type
Description
excludeSegments
string[]
Segment keys to exclude
Runtime: Compares segments from the Recommend request against excludeSegments. If the customer matches any, the candidate is blocked. If no segment data is available in the request, the engine fails open (allows the candidate through) to avoid blocking all offers when segment data is unavailable.
Restricts contacts to specific hours and/or days of the week. Supports IANA timezone strings validated at write time.Config fields:
Field
Type
Description
startHour
number (0-23)
Start of allowed window (inclusive)
endHour
number (0-23)
End of allowed window (exclusive)
daysOfWeek
string[]
Allowed days: Mon, Tue, Wed, Thu, Fri, Sat, Sun
timezone
string
IANA timezone (e.g. America/New_York). Falls back to UTC if omitted or invalid
Runtime: Converts the current time to the configured timezone, then checks both day-of-week and hour range. Overnight windows (e.g. startHour: 22, endHour: 6) are handled correctly.
Prevents competing Offers from being served to the same customer within a time window. If any Offer in the group has been shown recently, the others are suppressed.Config fields:
Field
Type
Description
offerGroup
string[]
List of Offer IDs that are mutually exclusive
suppressForDays
number
Days to suppress other group members after one is shown (default: 90)
Runtime: For each candidate Offer in the group, checks whether any other Offer in the group has an alltime summary with impressions > 0 and lastContactAt within suppressForDays.
Suppresses all Offers in a category (or sub-category) for a specified number of days after any Offer in that category was shown to the customer. This prevents fatigue from repeated pitches in the same product area.Config fields:
Field
Type
Description
categoryId
string
The Category ID to suppress
subCategoryId
string (optional)
Narrow to a specific sub-category
suppressionDays
number
Days to suppress after any category Offer was shown (default: 7)
Runtime: Builds a map of all Offer IDs in the target category from the current candidate set. Checks alltime summaries for any of those Offers. If any was shown within suppressionDays, all candidates in that category are blocked.
An override that allows contact despite other blocking policies. When the engine finds a matching allow_override, it skips all blocking rules for that candidate. Use this for mandatory or time-sensitive Offers that must bypass normal frequency limits.Config fields:
Field
Type
Description
allowSegments
string[]
(Optional) Only apply override if customer is in one of these segments
allowOfferIds
string[]
(Optional) Only apply override for these specific Offer IDs
Runtime:allow_override policies are evaluated before all blocking rules. If both allowSegments and allowOfferIds are set, both conditions must match. The engine logs a warning for every override to maintain an audit trail.
Use allow_override sparingly. Every override is logged with a warning. Consider making overrides time-bounded by pausing or archiving them after the campaign ends.
Blocks candidates when a behavioral metric value crosses a threshold. Supports dimension mapping to resolve metric values per candidate.
This rule type is implemented in the contact-policy engine and domain layer (contact-policy-rules.ts) but is not yet included in the API validation schema (api-validate.ts). To use it today, add it to the ruleType enum in the validation schema or write policies directly to the database.
Config fields:
Field
Type
Description
metricId
string
The behavioral metric to evaluate
operator
"gt" | "gte" | "lt" | "lte" | "eq"
Comparison operator (default: gte)
threshold
number
Value that triggers suppression
dimensionMapping
Record<string, string>
Maps metric dimensions to candidate fields (e.g. {"channel": "$candidate.channelId"})
Runtime: Looks up the metric value using the dimension mapping, applies the operator, and blocks if the condition is met.
Volume Constraints are system-wide caps that limit the total number of times an offer, category, or channel can be recommended across all customers within a time period. Unlike frequency caps (which are per-customer), volume constraints enforce business-level limits such as “no more than 20,000 email impressions per week” or “limit the Gold Card offer to 5,000 recommendations per month.”
Update an existing volume constraint (requires id)
DELETE
/api/v1/volume-constraints?id={id}
Delete a volume constraint
Use volume constraints alongside contact policies for complete control. Contact policies protect individual customers from over-contact; volume constraints protect your business from over-committing inventory or exceeding channel capacity.
Every Contact Policy operates within one of four scopes, which determines which candidates it applies to.
Scope
Matches When
Typical Use
global
Always matches every candidate
Company-wide compliance rules
offer
scopeId equals the candidate’s Offer ID
Product-specific frequency limits
creative
scopeId equals the candidate’s Creative ID
Creative-level fatigue rules
channel
scopeId equals the candidate’s Channel ID
Channel-specific time windows or caps
Not every rule type supports every scope. The table in the Rule Types section above shows allowed scopes per rule type. The UI dynamically filters the scope dropdown based on your selected rule type.
Use global scope for company-wide compliance rules (e.g. “no more than 5 contacts per week across all channels”) and narrower scopes for product-specific constraints.
Each Contact Policy has a priority value from 0 to 100 (default: 50). Higher values are evaluated first.The engine resolves conflicts as follows:
Sort all active policies by priority descending.
Separateallow_override policies from blocking policies.
For each candidate, check allow_override policies first. If any matches, the candidate is explicitly allowed and all blocking rules are skipped.
Otherwise, evaluate blocking policies in priority order. The first rule that blocks removes the candidate.
Unknown rule types fail open (the candidate is allowed through) with a warning log. This prevents misconfigured rule types from silently killing all offers, but also means invalid policies provide zero protection.
This means a priority-100 allow_override will beat a priority-100 frequency_cap because overrides are always checked first regardless of priority.
Starting with recent releases, contact policies are enforced via pre-computed suppression records rather than being evaluated live at decision time. This architecture dramatically reduces latency in the Recommend API by replacing per-policy evaluation with a single database read.
Write path (Respond API): When a respond call records an outcome that triggers a policy threshold (e.g., a frequency cap is reached, a cooldown begins), the engine writes a suppression record to the database with an expiry timestamp and the relevant scope (offer, creative, channel, or category).
Read path (Recommend API): At decision time, the engine loads all active (non-expired) suppressions for the customer in one query. Any candidate matching a suppression is immediately removed — no per-policy evaluation needed.
This means the cost of contact policy enforcement at decision time is constant regardless of how many policies are configured.
Not all policy types can be pre-computed. Policies that depend on the current request context (time of day, customer segments in the request payload) must still evaluate live.
The seven pre-computed types cover the vast majority of contact policies. The three live-evaluated types are inherently request-dependent and cannot be pre-computed.
Every suppression record embeds evidence — a JSON object containing the policy ID, rule type, the threshold that was crossed, and the interaction that triggered it. This evidence is preserved for the lifetime of the suppression and is surfaced in decision traces when debug mode is enabled.
When a customer repeatedly triggers the same policy, the suppression duration escalates automatically. Each suppression record tracks a triggerCount that increments on each re-trigger, and the engine looks up the matching escalation tier to determine how long the next suppression lasts.Configuration: Add an escalation array to any suppression-eligible policy’s config alongside the base cooldownHours:
Base suppression duration applies (e.g. cooldownHours: 48)
2
Matches trigger: 2 tier — suppressed for 14 days
3
Matches trigger: 3 tier — suppressed for 90 days
4+
Matches trigger: 4 with "action": "permanent" — suppressed for 10 years
Reset behavior: If the customer does not re-trigger the policy within 7 days of the current suppression’s expiry, the triggerCount resets to zero. The next violation starts fresh at tier 1.
Use escalating suppressions for policies like outcome_based (e.g. complaints) or cooldown where repeat offenders should face progressively longer quiet periods. The "permanent" action is implemented as a 10-year suppression to avoid infinite timestamps.
Customer C-4821 has already received 3 emails this week for the “Spring Promo” Offer. A frequency_cap policy limits the email channel to 3 per week.Policy:
The engine evaluates allow_override policies before blocking rules. Because this override matches the regulatory notice Offer, the frequency_cap is never checked for that candidate. The regulatory notice is delivered.The engine logs a warning:
WARN [contact-policy-engine] allow_override policy bypassing contact policy — ensure this override is time-bounded and approved { policyId: "cp_regulatory_override", offerId: "off_regulatory_notice" }
List policies (paginated, sorted by priority desc)
POST
/api/v1/contact-policies
admin
Create a new policy
PUT
/api/v1/contact-policies
admin
Update an existing policy (requires id in body)
DELETE
/api/v1/contact-policies?id={id}
admin
Soft-delete a policy
Deleting a policy uses soft-delete (the record is retained with a deletedAt timestamp). If the policy is referenced by any Decision Flow’s draftConfig, the response includes a warnings array listing affected flows (ghost reference check). Updates use auditedUpdate to create audit snapshots and increment rowVersion.See the API Reference for full request and response schemas.
Contact policies and volume constraints are complementary mechanisms that are both enforced during the Contact Policy pipeline stage, but they protect different things:
Contact Policies
Volume Constraints
Protects
Individual customers
The business
Purpose
Frequency caps, cooldowns, suppressions
Budget caps, inventory limits, channel quotas
Scope
Per-customer interaction history
System-wide counters across all customers
Evaluation order: Contact policies are evaluated first, then volume constraints filter the remaining candidates. A customer may pass all contact policy checks but still be blocked by a volume constraint if the offer, category, or channel has reached its delivery cap.
Use both together for complete control. Contact policies prevent individual customer fatigue; volume constraints prevent over-committing inventory or exceeding channel capacity. See the Volume Constraints page for configuration details.