# SendGrid Transactional Email

## Objective

Send account and product email through SendGrid and keep the delivery feedback.

A SendGrid delivery path for account email, with validated template data and recorded bounce and complaint outcomes.

## 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. Route transactional mail through the app's existing mail layer with SendGrid behind it as one adapter. The rest of the codebase must never construct provider-specific payloads directly, or swapping or adding a provider later means touching every send site.
2. Enumerate the app's transactional messages — verification, password reset, receipts, alerts — and give each a template with a declared set of variables that is validated before the message is queued.
3. Send from the app's background-job system with a stable per-message identifier, and store the provider's message identifier on the record as soon as it is returned so support can trace a specific email later.
4. Consume delivery feedback on a verified endpoint and let bounces and complaints update a suppression list that every future send checks. Feedback that is received and then discarded is worse than not collecting it.
5. Give non-production environments a mode that either suppresses delivery entirely or redirects every recipient to a fixed internal address, and make that the default outside production.

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

- Transactional mail and marketing consent are different things. A password reset must still go out to someone who unsubscribed from the newsletter, and a newsletter must never be sent under the transactional path to reach someone who opted out.
- A template rendered with a missing or misnamed variable produces an email with a visible gap or a broken link. Validate the variable set against the template's declared shape before the message is queued, and fail the job rather than sending it.
- Delivery, bounce, block, and complaint callbacks arrive on a public endpoint, can be replayed, and can arrive out of order. Verify their authenticity and process them idempotently so a redelivered event does not double-count or regress a record.
- Provider credentials and full message bodies must be kept out of logs and error reports. A stack trace that includes a reset link or a one-time code is a live credential sitting in log storage.
- A hard bounce or a spam complaint must suppress the address before the next send, not merely be recorded. Continuing to send to a bouncing address damages the sending reputation for every other message.
- A background job retried after a timeout must not send the email twice. Key the send on a stable identifier and reconcile on retry.
- The provider's rate limits and transient errors must be distinguished from permanent rejections. Back off and retry the former, stop immediately on the latter.
- When the provider is unavailable, the user-facing action must still succeed and the app must say the email is on its way, with the job retried, rather than reporting a failure the user cannot act on.

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

- [ ] All transactional email leaves through one mail layer with the provider behind an adapter.
- [ ] Every message type has a template with declared variables that are validated before queueing.
- [ ] Bounce, block, and complaint events are verified, processed idempotently, and update a suppression list checked on every send.
- [ ] Suppressed addresses are never sent to again.
- [ ] No log or error report contains provider credentials or full message bodies.
- [ ] Non-production environments suppress or redirect delivery by default.
- [ ] A retried job never sends the same email twice.
- [ ] 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.
