feat(teams): add createResendInvitationSender for the invitations seam#143
Merged
Conversation
Adopters back `sendInvitationEmail` with Resend and hand-roll the same wrapper —
and the same bug: `resend.emails.send()` returns `{ data, error }` and does NOT
throw on API-level failures (unverified domain, rate limit), so a try/catch alone
records a failed send as `emailStatus: 'sent'`. Both gtm-agent and creative-agent
carry this latent silent-failure.
Add an opt-in helper behind `@tangle-network/agent-app/teams/resend`:
createResendInvitationSender({ from, apiKey? }) returns a SendInvitationEmailSeam
that builds the client (apiKey ?? RESEND_API_KEY), renders via renderInvitationEmail,
and returns a typed failure on BOTH a thrown error AND a non-null result.error.
`resend` is an OPTIONAL peer (imported only here), so the core stays
provider-agnostic — apps not on Resend keep passing a raw seam.
tangletools
approved these changes
Jun 25, 2026
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved PR — fe20e359
Blanket team auto-approval is enabled for this reviewer service.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
tangletools · auto-approval · reason: blanket_auto_approve · 2026-06-25T04:14:23Z
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds an opt-in shared helper
createResendInvitationSender({ from, apiKey? }): SendInvitationEmailSeambehind a new subpath@tangle-network/agent-app/teams/resend, withresendas an optional peer.Why
The teams invitations module exposes
sendInvitationEmailas a per-app seam (so the core stays transport-agnostic). In practice every Resend-backed adopter hand-writes the same ~15-line wrapper — and both gtm-agent and creative-agent got it wrong the same way:resend.emails.send()returns{ data, error }and does not throw on API-level failures (unverified domain, rate limit, bad recipient). Atry/catchalone therefore records a failed send asemailStatus: 'sent'. Solving it once removes the silent-failure footgun fleet-wide.How
src/teams/resend.ts— builds the client lazily (apiKey ?? RESEND_API_KEY; typed failure when neither is set), renders via the existingrenderInvitationEmail(input, { fromAddress: from }), sends, and returns{ succeeded: false, error }on both a thrown error and a non-nullresult.error, else{ succeeded: true }.resendis an optional peer, imported only in this subpath (same pattern asdrizzle-orm/react/konva): apps not on Resend keep passing a raw seam, and the core never pulls a mail dependency. Invite-URL origin + from-address stay app-side (they're already injected params).Verification
resend): no-key, success,result.error, and thrown-error paths.pnpm vitest run tests/teams— 86 passed.pnpm typecheck— 0 errors.pnpm buildemitsdist/teams/resend.{js,d.ts}.Follow-ups (separate, in each app, after publish)
gtm-agent + creative-agent adopt
createResendInvitationSenderand delete their hand-rolled senders (which fixes creative's identical latent bug).