# Mailchimp Lead Sync

## Objective

Keep Mailchimp audiences current with app leads without creating duplicate contacts.

A one-way sync that pushes new and changed leads into a chosen Mailchimp audience with mapped fields and tags.

## Before You Begin

This feature is being added to an application that already exists and already
works. Do not scaffold a new project, and do not assume a blank slate.

Inspect the codebase first and establish:

- The existing application structure and where code of this kind already lives.
- The framework and version in use.
- The existing design system — colours, spacing, typography, and component conventions.
- Existing UI components you can reuse instead of writing new ones.
- The existing database structure, if this feature needs to persist anything.
- The existing authentication and authorization system, if this feature is user-scoped.
- Dependencies already installed, so you don't add a library that duplicates one.
- The existing test setup and conventions.

Only start writing code once you understand the above. If the application
already implements part of this feature, extend it rather than replacing it.

## Implementation Instructions

1. Find every place a lead is created or a profile changes — signup forms, imports, admin edits, checkout — and route all of them through a single sync path rather than calling the provider from each one.
2. Normalize the email address before matching: trim it, lowercase the domain, and resolve any display form to the plain address. Use that normalized value as the only match key, so the same person entering twice updates one member instead of creating a second.
3. Let a workspace admin choose the destination audience and map app fields and tags to it, and send only the fields the app genuinely owns. Leave everything else untouched rather than writing empty values over data the marketing team maintains.
4. Reuse the app's existing background-job system for every write, with retry and exponential backoff when the provider rate-limits, and never let a slow or failing provider hold up the response to the person signing up.
5. Do not treat the app as the authority on subscription state. When the provider reports a member as unsubscribed, cleaned, or archived, record that locally and stop sending — pushing them back to subscribed is how an app generates spam complaints.

## UI and UX Requirements

Match the application's existing design system exactly. Reuse its components,
spacing, and typography. This feature should look like it was always there.

## Responsive Requirements

Works on mobile, tablet, and desktop. Touch targets are large enough to hit on a
phone, and nothing overflows horizontally at 320px.

## Accessibility Requirements

- Fully keyboard navigable.
- Correct semantic elements and ARIA roles.
- Visible focus states.
- Meets WCAG AA contrast.
- Dynamic changes are announced to screen readers.
- Respects prefers-reduced-motion.

## Edge Cases

- Email addresses arrive with mixed casing, surrounding whitespace, and display-name wrappers. Without normalization before the lookup, the same person is matched as a new member each time and the audience fills with near-duplicates.
- Marketing teams enrich contacts by hand with data the app never had. A mapping that writes every mapped field on every update will erase that work, so send only fields the app owns and skip blanks rather than clearing the remote value.
- Members can be subscribed, unsubscribed, cleaned, pending, or archived, and each one means something different. Sending to a cleaned address damages sending reputation, and resubscribing an archived member without a fresh opt-in is a compliance problem.
- Both the provider's webhook retries and the app's own job retries will deliver the same event more than once. Every write needs a stable idempotency key derived from the lead and the change, or a single signup applies its tag three times and re-triggers the welcome automation.
- When a workspace disconnects its account, in-flight and queued jobs must stop immediately and stored credentials must be deleted. A queue that drains after disconnection keeps writing to an account the customer believes is detached.
- Stored tokens expire and can be revoked from the provider's side. Refresh ahead of expiry, and when refresh fails, mark the connection broken, surface it in the app, and hold events rather than discarding them.
- An audience can be deleted or renamed after the mapping is saved. Detect the missing destination, pause the sync with a clear message naming the audience, and do not silently fall back to a different one.
- When the provider is down, the app must still complete the signup and show success. Queue the sync, show its status on the lead record, and never present a provider outage as a failure of the user's own action.

## Testing

Exercise the feature end to end in the running application. Cover every edge case
above, then run the existing test suite and confirm nothing regressed.

## Acceptance Criteria

- [ ] A lead entered twice with differently formatted versions of the same address results in one member, not two.
- [ ] Repeated deliveries of the same event apply a tag or automation enrollment exactly once.
- [ ] Fields the app does not own, and blank app values, never overwrite existing member data.
- [ ] Unsubscribed, cleaned, and archived members are recorded locally and receive no further sends.
- [ ] Disconnecting an account halts queued and in-flight syncs and removes stored credentials.
- [ ] An expired or revoked token surfaces as a visible broken connection with events held rather than lost.
- [ ] Signup succeeds and the user sees success even while the provider is unreachable.
- [ ] The feature matches the existing design system.
- [ ] No existing functionality is broken.

## Adaptation Rules

- Match the existing design system. Do not introduce a new colour palette,
  spacing scale, or component library.
- Reuse existing components and utilities wherever they fit.
- Follow the naming, file layout, and code style already present.
- Do not upgrade, replace, or remove existing dependencies to make this
  feature fit. Adapt the feature to the app, not the app to the feature.
- Do not break existing functionality. If a change is genuinely required in
  existing code, make the smallest one that works and say so.
- If something in these instructions conflicts with how the application is
  built, follow the application and explain the deviation.

## Final Verification

Before you report the work as done:

1. Re-read the acceptance criteria above and check each one against what you
   actually built.
2. Run the application and exercise the feature end to end.
3. Run the existing test suite and confirm you have broken nothing.
4. Check the feature on mobile, tablet, and desktop widths.
5. Check keyboard navigation and focus handling.
6. Summarize what changed: files added, files modified, and anything you
   deliberately did differently because of how this application is built.

If any acceptance criterion is unmet, fix it before reporting completion.
