Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@devkiran
Copy link
Collaborator

@devkiran devkiran commented Jan 2, 2026

Summary by CodeRabbit

  • New Features

    • Added a rich email template for domain-status change notifications.
    • Cron endpoints now run under guarded wrappers to skip runs when not configured.
  • Bug Fixes

    • Per-domain verification now logs status changes and handles failures per-domain to avoid global failures.
    • Validation and early-exit checks during domain updates improved for reliability; simplified cron error flow.
  • Style

    • Minor component signature cleanup in an email template.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Contributor

vercel bot commented Jan 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
dub Ready Ready Preview Jan 2, 2026 5:21pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 2, 2026

📝 Walkthrough

Walkthrough

Replaces standalone cron route handlers with withCron-wrapped handlers for email-domain update and verify endpoints; removes QStash signature checks; adds per-domain verification loops, status-change detection, and conditional owner notifications; introduces an EmailDomainStatusChanged email template and removes an explicit return-type annotation from InvalidDomain.

Changes

Cohort / File(s) Summary
Cron handlers
apps/web/app/(ee)/api/cron/email-domains/update/route.ts, apps/web/app/(ee)/api/cron/email-domains/verify/route.ts
Replaced exported async handlers with withCron-wrapped exports. Removed QStash signature verification and global try/catch patterns. Update: parse rawBody, validate resend config, call resend.domains.update. Verify: fetches domains, runs per-domain verification, updates status/lastChecked, logs per-domain failures, and sends owner notifications on status changes.
Email templates — added
packages/email/src/templates/email-domain-status-changed.tsx
New React Email template EmailDomainStatusChanged and EmailDomainStatus type (pending, verified, failed, temporary_failure, not_started) used for owner notifications with dynamic messaging and CTA.
Email templates — minor signature change
packages/email/src/templates/invalid-domain.tsx
Removed explicit : JSX.Element return type annotation from InvalidDomain component signature; props types unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Cron as Cron Scheduler
  participant Handler as withCron Handler
  participant DB as Prisma (emailDomain)
  participant Resend as Resend API
  participant Mailer as Email service

  Cron->>Handler: trigger verify job
  Handler->>DB: findMany(emailDomain where resendDomainId != null, take 10)
  loop per domain
    Handler->>DB: fetch latest domain record
    Handler->>Resend: verify domain (resend.domains.verify)
    alt verification returns status
      Resend-->>Handler: status result
      Handler->>DB: update status, lastChecked
      alt status changed
        Handler->>DB: get workspace owners
        DB-->>Handler: owners list
        Handler->>Mailer: sendBatchEmail(EmailDomainStatusChanged)
        Mailer-->>Handler: send result
      else no change
        Handler-->>Handler: log no notification
      end
    else Resend API error
      Resend-->>Handler: error
      Handler-->>Handler: log per-domain failure
    end
  end
  Handler-->>Cron: complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 I hopped through cron at dawn so spry,
With withCron tucked beneath my sky.
Domains checked, their statuses sung,
Owners told — the meadow rung.
🥕📬

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main objective of the changeset, which is to implement email notifications when email domain status changes.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55dcce6 and 938897c.

📒 Files selected for processing (4)
  • apps/web/app/(ee)/api/cron/email-domains/update/route.ts
  • apps/web/app/(ee)/api/cron/email-domains/verify/route.ts
  • packages/email/src/templates/email-domain-status-changed.tsx
  • packages/email/src/templates/invalid-domain.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3213
File: apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts:122-122
Timestamp: 2025-12-15T16:46:01.529Z
Learning: In the Dub codebase, cron endpoints under apps/web/app/(ee)/api/cron/ use handleCronErrorResponse for error handling, which intentionally does NOT detect QStash callbacks or set Upstash-NonRetryable-Error headers. This allows QStash to retry all cron job errors using its native retry mechanism. The selective retry logic (queueFailedRequestForRetry) is only used for specific user-facing API endpoints like /api/track/lead, /api/track/sale, and /api/links to retry only transient Prisma database errors.
📚 Learning: 2025-12-09T12:54:41.818Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3207
File: apps/web/lib/cron/with-cron.ts:27-56
Timestamp: 2025-12-09T12:54:41.818Z
Learning: In `apps/web/lib/cron/with-cron.ts`, the `withCron` wrapper extracts the request body once and provides it to handlers via the `rawBody` parameter. Handlers should use this `rawBody` string parameter (e.g., `JSON.parse(rawBody)`) rather than reading from the Request object via `req.json()` or `req.text()`.

Applied to files:

  • apps/web/app/(ee)/api/cron/email-domains/update/route.ts
  • apps/web/app/(ee)/api/cron/email-domains/verify/route.ts
📚 Learning: 2025-12-15T16:45:51.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3213
File: apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts:122-122
Timestamp: 2025-12-15T16:45:51.667Z
Learning: In cron endpoints under apps/web/app/(ee)/api/cron, continue using handleCronErrorResponse for error handling. Do not detect QStash callbacks or set Upstash-NonRetryable-Error headers in these cron routes, so QStash can retry cron errors via its native retry mechanism. The existing queueFailedRequestForRetry logic should remain limited to specific user-facing API endpoints (e.g., /api/track/lead, /api/track/sale, /api/links) to retry only transient Prisma/database errors. This pattern should apply to all cron endpoints under the cron directory in this codebase.

Applied to files:

  • apps/web/app/(ee)/api/cron/email-domains/update/route.ts
  • apps/web/app/(ee)/api/cron/email-domains/verify/route.ts
🧬 Code graph analysis (2)
packages/email/src/templates/email-domain-status-changed.tsx (2)
packages/email/src/react-email.d.ts (11)
  • Html (4-4)
  • Head (5-5)
  • Preview (18-18)
  • Tailwind (19-19)
  • Body (6-6)
  • Container (7-7)
  • Section (8-8)
  • Img (13-13)
  • Heading (16-16)
  • Text (15-15)
  • Link (14-14)
packages/email/src/components/footer.tsx (1)
  • Footer (3-68)
apps/web/app/(ee)/api/cron/email-domains/verify/route.ts (4)
apps/web/lib/cron/with-cron.ts (1)
  • withCron (21-75)
apps/web/lib/api/get-workspace-users.ts (1)
  • getWorkspaceUsers (20-91)
packages/email/src/index.ts (1)
  • sendBatchEmail (31-70)
packages/email/src/templates/email-domain-status-changed.tsx (1)
  • EmailDomainStatusChanged (24-137)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (9)
packages/email/src/templates/invalid-domain.tsx (1)

27-27: LGTM! Clean removal of explicit return type.

The removal of the explicit JSX.Element return type is a good simplification, relying on TypeScript's type inference. The component's functionality remains unchanged.

apps/web/app/(ee)/api/cron/email-domains/update/route.ts (1)

15-55: Excellent refactoring with withCron wrapper!

The migration to withCron is well-executed:

  • Correctly uses rawBody parameter and parses it at the top level
  • Removes manual QStash verification (now handled by wrapper)
  • Error handling properly throws to trigger QStash retry mechanism
  • Early returns improve code clarity

This follows the established cron patterns in the codebase.

Based on learnings, the withCron wrapper handles signature verification and the error throwing pattern allows QStash native retry.

packages/email/src/templates/email-domain-status-changed.tsx (3)

17-22: LGTM! Well-defined status type.

The EmailDomainStatus type correctly covers all Resend domain statuses and is properly exported for use by consumers of this template.


24-46: Well-structured component with clear status handling.

The component props and status derivation logic are clean:

  • Props include all necessary context (domain, workspace, status change)
  • Status flags (isVerified, isFailed, isPending) appropriately group related statuses
  • Dynamic heading provides clear context for each scenario

Note: The default email value "[email protected]" appears to be a test/demo value, which is appropriate for component defaults.


61-127: Excellent conditional rendering with helpful user guidance.

The template provides appropriate messaging for each status:

  • Verified: Clear success message with action enablement
  • Failed: Helpful DNS configuration guidance
  • Pending: Sets proper expectations about timing
  • Other changes: Generic fallback with old/new status

The CTA button correctly links to the email domain settings page.

apps/web/app/(ee)/api/cron/email-domains/verify/route.ts (4)

14-44: Excellent refactoring with withCron and improved error handling!

The migration to withCron is well-executed:

  • Removes manual signature verification (now handled by wrapper)
  • Early return pattern when resend is not configured
  • Sequential processing with per-domain error handling is a good design choice here:
    • Processes domains in lastChecked order
    • Avoids overwhelming the Resend API with parallel requests
    • Isolates failures to individual domains without blocking the batch
    • Processing 10 domains sequentially is acceptable for an hourly cron

Based on learnings, this follows the established cron patterns in the codebase.


47-79: Smart status change detection prevents duplicate notifications.

The status change detection logic is well-implemented:

  • Updates database with new status and lastChecked timestamp
  • Compares against the previous status to detect changes
  • Early return prevents unnecessary notification processing
  • Logging provides good observability

This ensures users only receive emails when the status actually changes, not on every verification check.


85-96: Good filtering for notification recipients.

The workspace user query correctly:

  • Filters for owners only (appropriate privilege level for domain notifications)
  • Respects the domainConfigurationUpdates notification preference
  • Gracefully handles the case where no eligible users exist

This ensures notifications are sent only to users who want them.


98-128: Well-structured notification logic with appropriate error handling.

The email notification implementation is solid:

  • Dynamic subject line based on status (verified, failed, or generic change)
  • Batch email sending to all eligible users
  • Passes complete context to the template (domain, old/new status, workspace)
  • Logs email errors without throwing (correct because the domain status update has already succeeded and the notification is secondary)

The error handling strategy here is appropriate—the primary goal of updating the domain status is complete, and email delivery failures shouldn't cause the entire cron job to fail.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
apps/web/app/(ee)/api/cron/email-domains/verify/route.ts (1)

56-58: Consider logging the error when fetching domain from Resend fails.

Silently returning on error makes debugging harder. A log would help diagnose issues with specific domains.

🔎 Suggested improvement
  if (error) {
+   console.error(`Failed to fetch domain ${domain.slug} from Resend:`, error);
    return;
  }
packages/email/src/templates/email-domain-status-changed.tsx (1)

96-118: Pending status shows both generic and pending-specific messages.

When isPending is true, the template displays the generic "status changed from X to Y" message (lines 97-109) followed by the pending-specific message (lines 113-117). This might be intentional, but could feel redundant to users.

Consider consolidating into the main conditional:

🔎 Possible consolidation
-            ) : (
+            ) : isPending ? (
+              <Text className="text-sm leading-6 text-black">
+                Your email domain{" "}
+                <code className="text-purple-600">{domain}</code> for your Dub
+                workspace{" "}
+                <Link
+                  href={`https://app.dub.co/${workspace.slug}`}
+                  className="font-medium text-blue-600 no-underline"
+                >
+                  {workspace.name}↗
+                </Link>{" "}
+                is pending verification. This process may take a few minutes.
+                You'll receive another notification once verification is
+                complete.
+              </Text>
+            ) : (
               <Text className="text-sm leading-6 text-black">
                 ...
               </Text>
             )}
-
-            {isPending && (
-              <Text className="text-sm leading-6 text-black">
-                We're still verifying your domain...
-              </Text>
-            )}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13a8e41 and cb68ebc.

📒 Files selected for processing (4)
  • apps/web/app/(ee)/api/cron/email-domains/update/route.ts
  • apps/web/app/(ee)/api/cron/email-domains/verify/route.ts
  • packages/email/src/templates/email-domain-status-changed.tsx
  • packages/email/src/templates/invalid-domain.tsx
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3213
File: apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts:122-122
Timestamp: 2025-12-15T16:46:01.529Z
Learning: In the Dub codebase, cron endpoints under apps/web/app/(ee)/api/cron/ use handleCronErrorResponse for error handling, which intentionally does NOT detect QStash callbacks or set Upstash-NonRetryable-Error headers. This allows QStash to retry all cron job errors using its native retry mechanism. The selective retry logic (queueFailedRequestForRetry) is only used for specific user-facing API endpoints like /api/track/lead, /api/track/sale, and /api/links to retry only transient Prisma database errors.
📚 Learning: 2025-12-09T12:54:41.818Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3207
File: apps/web/lib/cron/with-cron.ts:27-56
Timestamp: 2025-12-09T12:54:41.818Z
Learning: In `apps/web/lib/cron/with-cron.ts`, the `withCron` wrapper extracts the request body once and provides it to handlers via the `rawBody` parameter. Handlers should use this `rawBody` string parameter (e.g., `JSON.parse(rawBody)`) rather than reading from the Request object via `req.json()` or `req.text()`.

Applied to files:

  • apps/web/app/(ee)/api/cron/email-domains/verify/route.ts
  • apps/web/app/(ee)/api/cron/email-domains/update/route.ts
📚 Learning: 2025-11-17T05:19:11.972Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3113
File: apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts:65-75
Timestamp: 2025-11-17T05:19:11.972Z
Learning: In the Dub codebase, `sendBatchEmail` (implemented in packages/email/src/send-via-resend.ts) handles filtering of emails with invalid `to` addresses internally. Call sites can safely use non-null assertions on email addresses because the email sending layer will filter out any entries with null/undefined `to` values before sending. This centralized validation pattern is intentional and removes the need for filtering at individual call sites.

Applied to files:

  • apps/web/app/(ee)/api/cron/email-domains/verify/route.ts
📚 Learning: 2025-12-15T16:45:51.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3213
File: apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts:122-122
Timestamp: 2025-12-15T16:45:51.667Z
Learning: In cron endpoints under apps/web/app/(ee)/api/cron, continue using handleCronErrorResponse for error handling. Do not detect QStash callbacks or set Upstash-NonRetryable-Error headers in these cron routes, so QStash can retry cron errors via its native retry mechanism. The existing queueFailedRequestForRetry logic should remain limited to specific user-facing API endpoints (e.g., /api/track/lead, /api/track/sale, /api/links) to retry only transient Prisma/database errors. This pattern should apply to all cron endpoints under the cron directory in this codebase.

Applied to files:

  • apps/web/app/(ee)/api/cron/email-domains/verify/route.ts
  • apps/web/app/(ee)/api/cron/email-domains/update/route.ts
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/cron/email-domains/update/route.ts (3)
apps/web/app/(ee)/api/email-domains/route.ts (1)
  • POST (39-143)
apps/web/lib/cron/with-cron.ts (1)
  • withCron (21-75)
packages/prisma/index.ts (1)
  • prisma (3-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (7)
packages/email/src/templates/invalid-domain.tsx (1)

27-27: LGTM!

Removing the explicit JSX.Element return type annotation is fine—TypeScript will correctly infer it.

apps/web/app/(ee)/api/cron/email-domains/update/route.ts (2)

15-16: Correct usage of withCron and rawBody pattern.

The implementation correctly uses rawBody from the withCron wrapper and parses it with the zod schema. Based on learnings, this is the expected pattern for QStash-triggered cron endpoints.


49-51: Appropriate error handling for QStash retries.

Throwing on Resend API failure allows QStash to retry transient errors, which aligns with the cron endpoint patterns in this codebase. Based on learnings, this is the correct approach.

apps/web/app/(ee)/api/cron/email-domains/verify/route.ts (3)

35-41: Good resilient batch processing pattern.

Individual try/catch blocks prevent one domain's failure from blocking others, and updating lastChecked ensures fair round-robin processing across all domains.


98-121: Email notification flow looks correct.

The subject line appropriately reflects the status change, and using sendBatchEmail with the notification preference filter ensures only interested owners receive emails. Based on learnings, sendBatchEmail handles filtering of invalid email addresses internally.


110-113: The domain.status and updatedDomain.status are already properly typed. The Prisma schema defines the status field as EmailDomainStatus enum, which matches the TypeScript type used in EmailDomainStatusChanged. Type safety is maintained at both the database and application levels.

Likely an incorrect or invalid review comment.

packages/email/src/templates/email-domain-status-changed.tsx (1)

17-36: Well-structured template with proper type exports.

The EmailDomainStatus type export enables type safety for consumers, and the default prop values support email previewing during development. Structure is consistent with other email templates in the codebase.

@devkiran
Copy link
Collaborator Author

devkiran commented Jan 2, 2026

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 2, 2026

✅ Actions performed

Full review triggered.

@steven-tey
Copy link
Collaborator

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 2, 2026

✅ Actions performed

Full review triggered.

@steven-tey steven-tey merged commit acec214 into main Jan 2, 2026
7 of 8 checks passed
@steven-tey steven-tey deleted the email-domains-notification branch January 2, 2026 17:28
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.

3 participants