# Constant Contact List Sync

## Objective

Put new leads on the right Constant Contact list with their consent recorded.

A background sync that creates or updates a Constant Contact contact and its list memberships whenever a lead is captured or its consent changes.

## 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 the app captures an email address with marketing intent — signup, waitlist, lead form, checkout opt-in — and treat each as a sync source whose destination list an operator configures. Where an event-forwarding brief is also applied to this app, that one owns behavioural events; this one owns contact identity, consent, and list membership.
2. Match on the email address and update the existing contact rather than inserting a new one. Reuse the app's existing background-job system so the sync never runs inside the request that captured the lead.
3. Carry the consent evidence the app already holds — the wording shown, the timestamp, the capture source, the request context — and preserve the permission status on every write. A sync must never upgrade a contact to subscribed as a side effect.
4. Read the contact's current list memberships before writing, and send the union of those and the lists this sync intends to add. Do not send only the lists this feature owns: a full replacement silently strips memberships someone else added.
5. Hold the account credential server-side and refresh it ahead of expiry. Do not place the token in client-visible code and do not call the provider from the browser, where anyone can read it and anyone can forge a lead.

## 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

- An address that already exists must update that contact rather than create a second one. Matching is case-insensitive and ignores surrounding whitespace.
- Permission-to-send status and the evidence behind it must survive every update. A contact who unsubscribed at the provider must not be resubscribed because the app still holds them as an active lead.
- A list deleted at the provider, or an account that has been disconnected, must fail the sync into a visible operator error with the affected leads retained, not drop them quietly.
- Reading memberships before writing them means two syncs racing on the same contact can each clobber the other. Serialise per contact so an update cannot remove a list added a moment earlier.
- A retried job must not trigger a second welcome message or automation series. Make each sync idempotent on the contact and apply only list changes that are genuinely new.
- Rate limits and provider outages need bounded backoff, and a lead that exhausts its attempts must land in a queue an operator can inspect and replay.
- An address the provider rejects as invalid or permanently bounced must be marked as such in the app rather than retried indefinitely.

## 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

- [ ] Every marketing-intent capture point in the app syncs to an operator-configured list.
- [ ] Syncing an address that already exists updates that contact instead of creating a duplicate.
- [ ] Consent status and its evidence are preserved on every write, and no sync subscribes a contact who opted out.
- [ ] List memberships added outside the app survive an update from the app.
- [ ] A retried or duplicated job produces no second welcome message.
- [ ] A deleted list or disconnected account surfaces as an operator-visible error with the affected leads queued for replay.
- [ ] No provider credential is reachable from client-side code.
- [ ] 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.
