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

Skip to content

Conversation

@devkiran
Copy link
Collaborator

@devkiran devkiran commented Nov 12, 2025

…ia Qstash integration

Summary by CodeRabbit

  • New Features

    • Added a background API endpoint to apply post-creation email domain configuration via queued requests.
  • Improvements

    • Domain configuration is now queued and processed asynchronously instead of applied immediately.
    • Stronger request validation and signature verification for queued tasks, with improved error handling and logging.
    • Ensures tracking and TLS settings are applied consistently during updates.

@vercel
Copy link
Contributor

vercel bot commented Nov 12, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Nov 12, 2025 1:08pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 12, 2025

Warning

Rate limit exceeded

@devkiran has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 13 minutes and 37 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between c5df345 and de93d94.

📒 Files selected for processing (2)
  • apps/web/app/(ee)/api/email-domains/[domain]/route.ts (2 hunks)
  • apps/web/app/(ee)/api/email-domains/route.ts (2 hunks)

Walkthrough

Adds a new cron POST endpoint that updates Resend domain tracking and refactors email-domain creation and update routes to enqueue that work via QStash (delayed) instead of performing Resend updates inline.

Changes

Cohort / File(s) Change Summary
New cron endpoint for domain updates
apps/web/app/(ee)/api/cron/email-domains/update/route.ts
Adds a POST handler that verifies QStash signature, validates payload with zod (domainId), loads the email domain (slug, resendDomainId), calls resend.domains.update (openTracking=true, clickTracking=false, TLS enforced), logs results, returns standardized success/error responses, and exports dynamic = "force-dynamic" and POST.
Refactor: enqueue domain update after creation
apps/web/app/(ee)/api/email-domains/route.ts
Replaces inline resend.domains.update after create with publishing a delayed QStash POST to the new cron endpoint (60s delay). Adds qstash and APP_DOMAIN_WITH_NGROK imports, checks response.messageId, and logs queue result.
Refactor: enqueue domain update after verify/update
apps/web/app/(ee)/api/email-domains/[domain]/route.ts
Removes inline resend.domains.verify/update calls and instead publishes a delayed QStash POST to the cron update endpoint (1-minute delay). Adds qstash and APP_DOMAIN_WITH_NGROK imports and logs queue acceptance/failure based on messageId.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant EmailDomainsRoute as /api/email-domains
    participant EmailDomainDetail as /api/email-domains/[domain]
    participant QStash
    participant CronEndpoint as /api/cron/email-domains/update
    participant Resend

    Client->>EmailDomainsRoute: Create domain
    EmailDomainsRoute->>Resend: resend.domains.create()
    Resend-->>EmailDomainsRoute: domain created
    EmailDomainsRoute->>QStash: publish POST (domainId) with 60s delay
    QStash-->>EmailDomainsRoute: messageId (or failure)
    EmailDomainsRoute-->>Client: creation response

    Client->>EmailDomainDetail: Verify/update domain
    EmailDomainDetail->>QStash: publish POST (domainId) with 60s delay
    QStash-->>EmailDomainDetail: messageId (or failure)
    EmailDomainDetail-->>Client: verify/update response

    rect rgb(230,245,255)
        Note over QStash,CronEndpoint: After delay
        QStash->>CronEndpoint: Deliver POST with domainId (QStash-signed)
        CronEndpoint->>CronEndpoint: Verify QStash signature & parse body
        CronEndpoint->>Resend: resend.domains.update(openTracking=true, clickTracking=false, tls=true)
        Resend-->>CronEndpoint: success or error
        CronEndpoint-->>QStash: log success/error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay close attention to QStash signature verification and usage of the raw request body in the cron handler.
  • Verify null/undefined handling for resendDomainId and logging branches in apps/web/app/(ee)/api/cron/email-domains/update/route.ts.
  • Confirm correct qstash.publish call shape and response.messageId checks in both modified email-domain routes.

Possibly related PRs

Suggested reviewers

  • steven-tey

Poem

🐇 I queued the hop, sixty ticks in tow,
A little cron to let the updates grow.
I verify the stash, then nudge Resend's door,
Open-tracks awake, clicks bother no more. ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: moving Resend domain update operations to a queue (QStash), which is the core modification across all three modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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: 1

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

66-69: Log the real error details

JSON.stringify(error) collapses Error instances to {}, so the message we send to log() drops the actual failure context. Please extract the message/stack before logging so we keep actionable diagnostics.

Apply this diff:

-    await log({
-      message: `Resend email domain update failed with error ${JSON.stringify(error)}`,
-      type: "errors",
-    });
+    const errorMessage =
+      error instanceof Error ? `${error.name}: ${error.message}` : JSON.stringify(error);
+    await log({
+      message: `Resend email domain update failed with error ${errorMessage}`,
+      type: "errors",
+    });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f617c0b and 8dd6d2d.

📒 Files selected for processing (2)
  • apps/web/app/(ee)/api/cron/email-domains/update/route.ts (1 hunks)
  • apps/web/app/(ee)/api/email-domains/route.ts (2 hunks)
⏰ 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

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: 1

♻️ Duplicate comments (1)
apps/web/app/(ee)/api/email-domains/route.ts (1)

91-115: Surface queue failures instead of silently logging.

This is the same concern from the previous review: placing qstash.publishJSON inside waitUntil means any queueing failure (missing credentials, network issues, Upstash outage) happens after the 201 response is already sent. The domain gets created but never receives the tracking update, and the caller has no way to detect or retry.

Move the publish call into the main request flow so failures are surfaced to the caller.

Apply this diff:

+      const response = await qstash.publishJSON({
+        url: `${APP_DOMAIN_WITH_NGROK}/api/cron/email-domains/update`,
+        method: "POST",
+        delay: 60,
+        body: {
+          domainId: emailDomain.id,
+        },
+      });
+
+      if (!response.messageId) {
+        throw new DubApiError({
+          code: "internal_server_error",
+          message: `Failed to queue email domain update for domain ${emailDomain.id}`,
+        });
+      }
+
       waitUntil(
         (async () => {
-          // Moving the updates to Qstash because updating the domain immeadiately after creation can fail.
-          const response = await qstash.publishJSON({
-            url: `${APP_DOMAIN_WITH_NGROK}/api/cron/email-domains/update`,
-            method: "POST",
-            delay: 1 * 60, // 1 minute delay
-            body: {
-              domainId: emailDomain.id,
-            },
-          });
-
-          if (!response.messageId) {
-            console.error(
-              `Failed to queue email domain update for domain ${emailDomain.id}`,
-              response,
-            );
-          } else {
-            console.log(
-              `Queued email domain update for domain ${emailDomain.id}`,
-              response,
-            );
-          }
+          console.log(
+            `Queued email domain update for domain ${emailDomain.id}`,
+            response.messageId,
+          );
         })(),
       );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8dd6d2d and f38a1c5.

📒 Files selected for processing (1)
  • apps/web/app/(ee)/api/email-domains/route.ts (2 hunks)
⏰ 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 (1)
apps/web/app/(ee)/api/email-domains/route.ts (1)

6-6: LGTM!

The new imports are necessary for queueing domain updates via QStash.

Also applies to: 13-13

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: 1

🧹 Nitpick comments (1)
apps/web/app/(ee)/api/email-domains/[domain]/route.ts (1)

74-84: Consider adding monitoring for queue failures.

The code only logs when queuing fails but doesn't raise an alert or throw an error. While this is appropriate within a waitUntil block, silent queue failures could lead to domains remaining in "pending" status indefinitely without proper updates.

Consider implementing monitoring or alerting for queue failures to ensure operational visibility.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f38a1c5 and c5df345.

📒 Files selected for processing (1)
  • apps/web/app/(ee)/api/email-domains/[domain]/route.ts (2 hunks)
⏰ 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 (3)
apps/web/app/(ee)/api/email-domains/[domain]/route.ts (3)

6-6: LGTM! Imports are appropriate for the queuing mechanism.

The new imports for qstash and APP_DOMAIN_WITH_NGROK are correctly added to support the background job queuing.

Also applies to: 13-13


62-86: The cron endpoint is properly implemented with robust error handling and retry logic.

Verification confirms the target endpoint (/api/cron/email-domains/update) exists and correctly handles the queued domain update. The implementation includes QStash signature verification, domain validation, proper error handling, and automatic retry logic through QStash's error propagation mechanism. The refactoring is sound.


66-66: APP_DOMAIN_WITH_NGROK is correctly configured for production environments.

The constant uses a three-tier environment strategy: production resolves to https://app.${process.env.NEXT_PUBLIC_APP_DOMAIN}, preview resolves to https://preview.${process.env.NEXT_PUBLIC_APP_DOMAIN}, and development falls back to ngrok or localhost. The name is potentially misleading (suggesting ngrok is always used), but the implementation explicitly checks for production and resolves to the proper production domain. Queued jobs in production will correctly route to the app domain, not a development tunnel.

@devkiran devkiran merged commit d9b2eae into main Nov 12, 2025
7 of 8 checks passed
@devkiran devkiran deleted the debug-resend-issue branch November 12, 2025 13:11
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