feat(analyzer): phase 7 — share-via-email
sendAnalysisShareEmail() reuses the existing nodemailer SMTP transport
(same path as magic-link/invitation mail). Share route persists the
audit row first, then attempts send; on failure returns
{share, emailSent:false, emailError} at HTTP 200 so the audit log
stays intact. Modal surfaces send failures as a warning toast.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8f8b5ab7be
commit
ed3b363d02
4 changed files with 231 additions and 9 deletions
|
|
@ -318,6 +318,64 @@ This file is updated after each phase ships.
|
|||
|
||||
---
|
||||
|
||||
## Phase 7 — Share-via-email integration
|
||||
|
||||
**Delivered**
|
||||
|
||||
- `sendAnalysisShareEmail()` added to `lib/services/email.ts` — re-uses the
|
||||
existing nodemailer SMTP transport that already serves magic-link and
|
||||
invitation mail. Subject `Pulse analysis · <ticket> v<n> — <summary
|
||||
excerpt>`, gradient-header HTML body matching the other Pulse emails, plain
|
||||
text fallback, `replyTo` set to the sharer so a recipient reply lands with
|
||||
the right person.
|
||||
- Share route `app/api/analyzer/analyses/[id]/share/route.ts` now resolves
|
||||
the sharer's session, builds the analysis URL from `BETTER_AUTH_URL`
|
||||
(falling back to `NEXT_PUBLIC_BETTER_AUTH_URL`, then `localhost:3100`),
|
||||
persists the audit row, then attempts the email send.
|
||||
- Share-modal frontend handles the new `emailSent`/`emailError` fields:
|
||||
success → green toast; row-saved-but-send-failed → orange `toast.warning`
|
||||
carrying the SMTP error.
|
||||
|
||||
**Decisions worth flagging**
|
||||
|
||||
- **Audit row persists even if email send fails.** The `analyzer_shares`
|
||||
row is the audit log, not just a delivery receipt. SMTP outages should
|
||||
not erase the record that the user attempted a share.
|
||||
- **Response is HTTP 200 on email failure.** The route returns
|
||||
`{share, emailSent: false, emailError}`. A non-2xx would imply the share
|
||||
itself failed; surfacing `emailSent: false` is the more honest signal.
|
||||
- **No `email_sent_at` column added.** Adding it requires a migration and
|
||||
a way to retry — neither is asked for by the spec. Send state lives only
|
||||
in the response and the server log line `[ANALYZER-SHARE] email send
|
||||
failed for share <id>`. If retries become a real need, a dedicated
|
||||
`analyzer_share_email_attempts` table is the natural shape.
|
||||
- **Used existing nodemailer SMTP, not Graph sendMail.** Spec said "via
|
||||
M365 Graph using existing wulf-pulse mail integration if one exists;
|
||||
otherwise use a new module" — `email.ts` *is* the existing module. The
|
||||
Graph integration in this codebase is read-only (mailbox search, user
|
||||
reports), not send-capable.
|
||||
- **HTML escaping is hand-rolled.** Five-replace function for
|
||||
amp/lt/gt/quote/apos. The user-controlled fields (sender name, note,
|
||||
analysis summary, next step) all flow through it. The repo has no HTML
|
||||
escaper utility and the magic-link/invitation emails don't need one
|
||||
because their inputs are URLs and admin-set names.
|
||||
|
||||
**Deliberately left out**
|
||||
|
||||
- **No retries.** A failed send is logged once and surfaced to the user.
|
||||
They can re-share if they want — that creates a fresh audit row, which
|
||||
is correct behavior.
|
||||
- **No tests.** `lib/services/email.ts` has no existing tests, mocking
|
||||
nodemailer fully would add a non-trivial test scaffold for one
|
||||
function, and the share route is route-handler thin. Consistent with
|
||||
phases 5/6.
|
||||
- **No `viewed_at` tracking yet.** The migration has the column but
|
||||
nothing writes to it. A `/share/:id/viewed` endpoint with an
|
||||
unguessable token would be the smallest addition; spec didn't ask, so
|
||||
skipped.
|
||||
|
||||
---
|
||||
|
||||
## Status after each phase
|
||||
|
||||
| Phase | Tests | tsc | Notes |
|
||||
|
|
@ -328,3 +386,4 @@ This file is updated after each phase ships.
|
|||
| 4 | 128 | clean | + pipeline + worker |
|
||||
| 5 | 128 | clean | API routes (no route tests) |
|
||||
| 6 | 128 | clean | frontend (no FE tests) |
|
||||
| 7 | 128 | clean | share email via existing SMTP transport |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue