Skip to content

feat: broadcast metrics and recipients - #1037

Open
dielduarte wants to merge 5 commits into
preview-broadcast-metricsfrom
broadcast-metrics
Open

feat: broadcast metrics and recipients#1037
dielduarte wants to merge 5 commits into
preview-broadcast-metricsfrom
broadcast-metrics

Conversation

@dielduarte

@dielduarte dielduarte commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Corrects broadcasts.metrics()'s response type to match the real API shape: { total, percentage } counters (was { count, rate }), plus status, created_at, scheduled_at, sent_at, remaining, and clicked_links.
  • Adds broadcasts.recipients() to list a broadcast's recipients filtered by event type (sent, delivered, opened, clicked, bounced, complained, unsubscribed, suppressed), with cursor pagination and email/bounceType filters.
  • recipients()'s response type is generic over the request's type: a literal type (the common case) gives precisely-typed fields with no null-checks (e.g. clicked guarantees count/clicked_links); a dynamically-typed type value degrades those fields to optional instead of failing to compile.

Based on resend-docs#1677.

Test plan

  • broadcasts.spec.ts covers metrics() with the corrected shape, and recipients() for not-found, type filtering, email/bounceType filters, and both after/before cursor pagination
  • Full test suite passes (392 tests)
  • Verified the generic type behavior compiles correctly via a standalone tsc check (precise fields for literal type, optional fields for dynamic type, invalid combinations rejected)

🤖 Generated with Claude Code


Summary by cubic

Add broadcast analytics and recipient listing, released as preview 6.19.0-preview-broadcast-metrics.0.

  • New Features

    • broadcasts.metrics() returns aggregate counters as { total, percentage }, plus status, created_at, scheduled_at, sent_at, sent, remaining, and clicked_links.
    • broadcasts.recipients() lists recipients by event type (sent, delivered, opened, clicked, bounced, complained, unsubscribed, suppressed) with cursor pagination and email/bounceType filters. The response is generic to the requested type: literal types guarantee fields; unions or dynamic values fall back to a safe loose shape.
  • Refactors

    • Replaced nested conditional types with a per-type field map guarded by IsUnion for BroadcastRecipient, preventing partial unions from dropping fields while keeping precise types for single-literal type.

Written for commit 55d348c. Summary will update on new commits.

Review in cubic

dielduarte and others added 2 commits July 27, 2026 14:43
…point

Fixes the metrics response to match the real API shape (total/percentage
instead of count/rate, plus status/timestamps/clicked_links), and adds
broadcasts.recipients() to list a broadcast's recipients filtered by
event type. The response type is generic over the request's `type`, so
a literal type gets precisely-typed fields while a dynamic type value
degrades to optional fields instead of failing to compile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-new Bot commented Jul 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/resend@1037

commit: 55d348c

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/broadcasts/broadcasts.spec.ts
Comment thread src/broadcasts/interfaces/list-broadcast-recipients.interface.ts Outdated
Comment on lines +161 to +167
if (email !== undefined) {
searchParams.set('email', email);
}

if (bounceType !== undefined) {
searchParams.set('bounce_type', bounceType);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keeping the codebase pattern, but I think != null would be safer.

A T like 'opened' | 'clicked' distributed member-by-member across the
per-field conditionals and lost clicked_links entirely instead of
making it optional, since the fields' shapes don't recombine correctly
across separately-distributed intersections. Switch to a non-distributive
exhaustive match against each single literal, so anything that isn't
exactly one literal (partial unions included) falls back to the safe
BroadcastRecipientLoose shape.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dielduarte
dielduarte marked this pull request as ready for review July 27, 2026 18:42
@dielduarte
dielduarte requested a review from a team as a code owner July 27, 2026 18:42
@dielduarte
dielduarte requested a review from joaopcm July 27, 2026 18:42

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Auto-approved: Adds two bounded, well-tested broadcast API methods with corrected response types and generic recipient filtering. No operational or security tradeoffs.

Re-trigger cubic

count: number;
clicked_links: BroadcastRecipientClickedLink[];
}
: [T] extends ['opened']

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fun types 👀

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes! love TS! kinda nostalgic to think about it nowadays

@dielduarte
dielduarte changed the base branch from canary to preview-broadcast-metrics July 27, 2026 21:18
Minor bump for the new broadcasts.recipients() endpoint, tagged as a
preview since broadcast metrics/recipients are still in private beta.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Auto-approved: Adds two bounded, clearly beneficial API surface additions (broadcast metrics and recipients) with correct types and comprehensive tests.

Re-trigger cubic

@github-actions github-actions Bot added the linear-synced PR has been synced to Linear label Jul 28, 2026
Comment thread src/broadcasts/interfaces/list-broadcast-recipients.interface.ts Outdated
Comment thread src/broadcasts/interfaces/list-broadcast-recipients.interface.ts Outdated
Comment thread src/broadcasts/interfaces/list-broadcast-recipients.interface.ts Outdated
…cipient

Addresses review feedback: index into a per-type field map guarded by an
IsUnion check, instead of a chain of tuple-wrapped conditional types.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Requires human review: Adds new broadcast metrics and recipients API methods, expanding the library's public contract. The non-trivial generic type for recipients and the new API endpoints benefit from human review to ensure alignment with the external Resend API.

Re-trigger cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do not merge linear-synced PR has been synced to Linear

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants