Skip to content

feat(invitations): card-style invite email with kind-0 team profile#33

Merged
alejandro-runner merged 1 commit intosynvya-stagingfrom
feat/invitation-email-card
Apr 22, 2026
Merged

feat(invitations): card-style invite email with kind-0 team profile#33
alejandro-runner merged 1 commit intosynvya-stagingfrom
feat/invitation-email-card

Conversation

@alejandro-runner
Copy link
Copy Markdown
Member

Summary

Rework the team-invitation email so it mirrors the accept-invite page recipients see when they click through: centred Synvya wordmark, Team Invitation heading, white card with the team's avatar + display name, inviter / recipient / expiry line, and a prominent Accept Invitation button.

Solves two issues with the old email:

  • Showed a truncated pubkey (f7ebfcf1…) instead of the admin's identity when the admin had no display_name / username set. Now falls back to the admin's email.
  • Showed the raw team handle (elcandado) with no avatar. Now fetches kind-0 for the team's stored key from BUNKER_RELAYS and uses the same display_name + picture the accept page renders, so the two views match.

Changes

  • New api/src/nostr_profile.rsfetch_profile_metadata(pubkey, relays) with a 3s timeout. Returns None on any failure (bad pubkey, unreachable relay, timeout, no kind-0, malformed JSON) so the email still sends with a sensible fallback.
  • api/src/email_service.rs — extended EmailSender::send_team_invite_email to accept team_picture: Option<&str>, expires_at, and a renamed inviter_label; rewrote team_invite_html / team_invite_text as the card layout. HTML-escapes all interpolated values; avatar URL restricted to http/https.
  • api/src/api/http/teams.rs — in the invite handler, resolve inviter email, load team key pubkey, call kind-0 fetch, pass real display name / picture / expiry / inviter label. Existing InvitationListItem response still uses the previous resolve_display_name behaviour.
  • api/src/lib.rs — exports the new module.

Fallback behaviour

Scenario Email shows
Admin has display_name set Display name (unchanged)
Admin has no display name, has email Admin email (new)
Admin has neither pubkey[..8]… (unchanged)
Team stored-key has kind-0 with display_name Kind-0 display name
No kind-0 / relay timeout / no stored key team.name handle (unchanged)
Kind-0 has valid http/https picture Avatar rendered in card
picture missing or non-http Card rendered without avatar (no broken image)

Out of scope

Visual consistency pass for verification / password_reset / claim emails — tracked separately. Those don't have an entity to present, so the card pattern doesn't transfer; a lighter shared header/footer/button treatment can follow.

Test plan

  • cargo check --workspace clean
  • cargo check -p keycast_api --features aws clean
  • cargo clippy -p keycast_api --all-targets -- -D warnings clean
  • cargo test -p keycast_api --lib — 62 passed (5 new for the kind-0 parser)
  • Staging smoke test: invite a new email to a team, confirm email shows inviter email, team display name, and avatar
  • Confirm an invitation still goes out if BUNKER_RELAYS is briefly unreachable (fallback to DB handle, no avatar)

🤖 Generated with Claude Code

Rework the team-invitation email so it mirrors the accept-invite page:
centred Synvya wordmark, "Team Invitation" header, white card with the
team's avatar and display name, inviter + recipient + expiry, and a
prominent Accept Invitation button.

- New api/src/nostr_profile.rs fetches kind-0 metadata (display_name,
  picture) from BUNKER_RELAYS with a 3s timeout; returns None on any
  failure so the email still sends with the DB handle as fallback.
- Prefer inviter email over the pubkey-prefix fallback when the admin
  has no display_name/username, so recipients see "staging@…" instead
  of "f7ebfcf1…".
- Team display name + avatar in the email now match what the accept
  page shows, because both derive from the same kind-0 record.
- HTML-escape all interpolated values and restrict avatar URLs to
  http/https.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@alejandro-runner alejandro-runner merged commit d4de85e into synvya-staging Apr 22, 2026
@alejandro-runner alejandro-runner deleted the feat/invitation-email-card branch April 22, 2026 02:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant