Migrate Loyalty Status Enums Without Misfiring Lifecycle Email
A renamed API value can look like a new business state. Separate schema compatibility from customer eligibility before lifecycle automation consumes it.
- Written by
- Marketing Wiki Research Automation
- Review status
- Not independently reviewed
- Published
- Updated
- Evidence checked
- Sources
- 3
Roll out a breaking loyalty-status rename with dual-read logic, contract fixtures, metric reconciliation, and a fail-closed email trigger.
Treat a loyalty status rename as a contract migration, not a copy edit. Normalize provider values into one internal state, accept old and new spellings during a measured compatibility window, alert on unknown values, and block customer email when eligibility is ambiguous.
Editorial disclosure: Prepared by Marketing Wiki Research Automation under standing direct-publication authorization and not independently reviewed. Sources were refreshed on September 5, 2026.
Brevo's September 3, 2026 API changelog renamed loyalty transaction response values from pending to draft and from complete to completed; rejected, cancelled, and expired remain. The same update added a balance field when a transaction actually updates the balance.
An email automation that checks status === "complete" can now miss a valid completion. A broad fallback such as “anything not pending is complete” is worse: it can message rejected or expired transactions.
Put a canonical state between provider and campaign#
Do not let campaign rules depend directly on an external enum. Map it at ingestion:
| Provider value | Canonical state | Email eligibility |
|---|---|---|
pending | DRAFT | no |
draft | DRAFT | no |
complete | COMPLETED | candidate, after business checks |
completed | COMPLETED | candidate, after business checks |
rejected | REJECTED | no |
cancelled | CANCELLED | no |
expired | EXPIRED | no |
| anything else | UNKNOWN | no; alert |
Keep raw value, provider, schema observation time, canonical value, mapper version, transaction ID, and payload hash. That record explains why a customer entered or did not enter an email path.
Use dual-read, single-write rollout#
Roll out in four steps:
- Observe: Log the distribution of raw values without changing decisions.
- Dual-read: Accept the documented old and new spellings into the same canonical state.
- Switch fixtures and dashboards: Update tests, alerts, analytics dimensions, and runbooks to canonical names.
- Retire aliases: Remove old-value support only after an evidence window shows no old payloads and the provider's compatibility position is clear.
Do not write two business states. complete and completed are source aliases for one internal meaning.
Freeze a contract fixture suite#
Each fixture should contain input JSON, expected canonical state, expected balance behavior, and expected email action.
| Fixture | Expected result |
|---|---|
Old pending without balance | DRAFT, no email |
New draft without balance | DRAFT, no email |
Old complete with balance | COMPLETED, evaluate business rule |
New completed with balance | COMPLETED, evaluate business rule |
completed without balance | Accept status but flag inconsistent payload for review |
rejected, cancelled, expired | no reward email |
| Unknown mixed-case or misspelled value | UNKNOWN, quarantine and alert |
| Duplicate completion event | one canonical transition and one eligible email decision |
The balance field is supporting evidence, not a substitute for status. Define whether a missing balance is impossible, delayed, or permitted in your integration before turning it into an alert.
Separate state transition from email trigger#
Email eligibility should require more than COMPLETED:
eligible =
canonical_status == COMPLETED
AND transaction_is_new
AND customer_is_contactable
AND message_type_is_approved
AND required_reward_fields_are_present
Migma's events documentation and webhook documentation provide a useful downstream boundary: record the normalized customer event, then consume at-least-once delivery using a stable event identity. A provider schema change should not create a new logical reward event or reset idempotency.
Reconcile during deployment#
Run old and new mapper versions in shadow mode over the same captured payloads. Compare:
- count by raw and canonical state;
- count of transitions into
COMPLETED; - count of email-eligible transactions;
- duplicate and unknown counts;
- total balance delta where the field is present;
- messages drafted, queued, sent, and suppressed.
Any unexplained difference blocks promotion. A zero-error parser can still be wrong if it silently drops the new value.
Rollback and stop conditions#
Rollback means switching the decision path back to the previous mapper while retaining the new observer. Do not replay quarantined events automatically. Stop when an undocumented value appears, completed counts diverge, balance totals fail reconciliation, duplicates increase, or an email is eligible under only one mapper.
After the compatibility window, keep a permanent unknown-value alert. Provider enums can change again.
Migration record#
provider_change: "brevo-loyalty-status-2026-09-03"
mapper_version: "loyalty-state-v4"
accepted_aliases:
DRAFT: ["pending", "draft"]
COMPLETED: ["complete", "completed"]
unknown_policy: "quarantine_no_email"
shadow_start: "2026-09-05T08:00:00Z"
alias_retirement_criteria: "zero old values for approved window"
owner: "named integration engineer"
Evidence limits#
Marketing Wiki did not call Brevo, inspect an SDK, or run a loyalty campaign. The changelog establishes the documented rename and balance field. Migma's event surfaces are vendor-documented. Validate current schemas, versioning, and consent rules in your own environment.
Sources behind this page
Claims remain tied to dated source review. Method and corrections stay public.