') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); rebrand to useSend by KMKoushik · Pull Request #210 · usesend/useSend · GitHub
Skip to content

rebrand to useSend - #210

Merged
KMKoushik merged 17 commits into
mainfrom
km/2025-08-30-rebrand
Sep 2, 2025
Merged

rebrand to useSend#210
KMKoushik merged 17 commits into
mainfrom
km/2025-08-30-rebrand

Conversation

@KMKoushik

@KMKoushikKMKoushik commented Aug 31, 2025

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Optional USESEND_API_KEY, X-Usesend-Email-ID support, and configurable DKIM selector.
  • Improvements

    • Bulk send: template caching, domain grouping, and preserved ordering.
    • Default SMTP host/user updated to usesend; UI/SDK rebranded; new typography primitives (H1/H2/BodyText).
  • Deprecations

    • SDK rebranded to UseSend; Unsend retained as deprecated alias.
  • Documentation

    • Full rebrand across docs, examples, and README.
  • Chores

    • Package names, scripts, CI, and image targets migrated to usesend.
  • Removal

    • Marketing app simplified; multiple marketing pages/components removed.

@coderabbitai

coderabbitaiBot commented Aug 31, 2025

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Project-wide rebrand from Unsend → useSend: package and config renames, UI import migrations, marketing app trimmed, mailer switched to UseSend with USESEND_API_KEY support, SES/DKIM header and selector additions, bulk email renderer caching/order fixes, Prisma migration adding dkimSelector, and CI/docs/docker renames.

Changes

Cohort / File(s)Summary
Root & workspace manifests
./package.json, ./.eslintrc.js, tsconfig.json, packages/*/package.json
Renamed packages/references from unsend/@unsend/* → usesend/@usesend/*; updated root scripts (added lint/format/db scripts) and ESLint/TS base refs.
Marketing app (trimmed / rewrites)
apps/marketing/** (many files removed/rewritten), apps/marketing/next.config.js, apps/marketing/postcss.config.cjs, apps/marketing/next-env.d.ts
Removed many marketing pages/components, added minimal landing page, new Next/PostCSS/TS configs and assets, simplified layout and fonts.
Web app config & env
apps/web/.eslintrc.cjs, apps/web/tsconfig.json, apps/web/src/env.js, .env.example, .env.selfhost.example, apps/web/tailwind.config.ts
Switched ESLint/TS/Tailwind refs to @usesend/*; added optional USESEND_API_KEY and runtime mapping; changed SMTP defaults to usesend hosts/users; updated env samples.
Mailer & SMTP proxy
apps/web/src/server/mailer.ts, apps/smtp-server/src/server.ts, apps/smtp-server/docker-compose.yml, apps/smtp-server/src/usage.js
Replaced Unsend client with UseSend, prefer USESEND_API_KEY (fallback to UNSEND_API_KEY), rebranded subjects/logs, renamed sendEmail function, updated env keys and Docker names.
SES & DKIM handling
apps/web/src/server/aws/ses.ts, apps/web/src/server/service/ses-hook-parser.ts, apps/web/src/server/service/domain-service.ts
addDomain now accepts dkimSelector (default "usesend"); SES destination names rebranded; webhook parsing accepts X-Usesend-Email-ID alongside legacy header; domain-service persists dkimSelector.
Bulk email & renderer improvements
apps/web/src/server/service/email-service.ts, apps/web/src/server/api/routers/{campaign,template}.ts, apps/web/src/app/api/to-html/route.ts
sendBulkEmails groups by sender domain, adds per-template cache and renderer reuse, preserves input order via originalIndex, queues jobs with timestamps; renderer imports updated to @usesend/email-editor.
Mass import / branding updates
apps/web/src/**, packages/email-editor/src/**, packages/ui/**, many other files
Hundreds of import-path and branding text updates from @unsend/* → @usesend/* and Unsend → useSend across UI, editor, server, docs and examples.
UI library additions & tweaks
packages/ui/src/typography.tsx, packages/ui/src/sheet.tsx, packages/ui/index.ts
Added H1/H2/BodyText typography primitives and exported them; adjusted Sheet styling/variants and allowed variant props on SheetContent.
SDK & package exports
packages/sdk/index.ts, packages/sdk/package.json, packages/* tsconfig/.eslintrc files
SDK primary export changed to UseSend with deprecated Unsend alias; package names/version bumped and tooling refs updated to @usesend/*.
Prisma migration & schema
apps/web/prisma/migrations/*, apps/web/prisma/schema.prisma
Added nullable Domain.dkimSelector column and migration that sets existing rows and default to "usesend".
Email templates, copy & errors
apps/web/src/server/email-templates/**, apps/web/src/lib/constants/ses-errors.ts
Updated default texts, placeholders and user-facing error messages from Unsend → useSend/usesend; added usesend_unsubscribe_url substitution.
Docs, CI & Docker
README.md, CONTRIBUTION.md, apps/docs/**, .github/**, docker/**, .github/workflows/publish.yml
Broad rebranding of docs/CI/docker images and publish targets from unsend → usesend; links, badges and examples updated.
Deleted docs & marketing assets
CLAUDE.md, many apps/marketing/* files, apps/marketing/.gitignore
Removed several documentation and marketing asset files.
Formatting-only
Many files across repo
Numerous trailing commas, whitespace and minor formatting edits with no semantic change.

Sequence Diagram(s)

sequenceDiagram
autonumber
participant Client as API Caller
participant EmailSvc as EmailService
participant DB as Database
participant Renderer as EmailRenderer (cached per template)
participant Queue as Job Queue
Client->>EmailSvc: sendBulkEmails(inputs[])
EmailSvc->>EmailSvc: group inputs by sender domain
loop per template
alt template cached
EmailSvc->>Renderer: reuse cached renderer
else
EmailSvc->>DB: load template JSON
DB-->>EmailSvc: template JSON
EmailSvc->>Renderer: prepare renderer and cache
end
end
loop per input (preserve originalIndex)
EmailSvc->>Renderer: render HTML (adds usesend_unsubscribe_url)
EmailSvc->>DB: create Email record (store originalIndex)
EmailSvc->>Queue: enqueue job { emailId, teamId, timestamp, region, transactional }
end
EmailSvc-->>Client: return created+suppressed sorted by originalIndex
Loading
sequenceDiagram
autonumber
participant SES as AWS SES
participant Webhook as App Webhook
participant Parser as parseSesHook
participant DB as Database
SES-->>Webhook: SES Notification (headers, sesEmailId)
Webhook->>Parser: parseSesHook(event)
alt find by sesEmailId
Parser->>DB: lookup by sesEmailId
else
Parser->>Parser: read X-Usesend-Email-ID or X-Unsend-Email-ID
Parser->>DB: lookup by header emailId
Parser->>DB: update email.sesEmailId
end
Parser-->>Webhook: parsed result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

In my burrow I changed the name,
From Unsend hops to UseSend fame.
I cache templates, queue with care,
Add headers both old and fair.
A little hop — the release is there. 🐇✨

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch km/2025-08-30-rebrand

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
apps/web/src/lib/constants/example-codes.ts (1)

66-66: Incomplete rebrand - API endpoints still reference unsend.dev

While the Node.js and Python examples have been updated to use usesend.com, the PHP, Ruby, and cURL examples still reference the old unsend.dev domain.

Apply these fixes:

For PHP (line 66):

- CURLOPT_URL => "https://app.unsend.dev/api/v1/emails",+ CURLOPT_URL => "https://app.usesend.com/api/v1/emails",

For Ruby (line 100):

-url = URI("https://app.unsend.dev/api/v1/emails")+url = URI("https://app.usesend.com/api/v1/emails")

For cURL (line 129):

-curl -X POST https://app.unsend.dev/api/v1/emails \+curl -X POST https://app.usesend.com/api/v1/emails \

Also applies to: 100-100, 129-129

apps/web/src/server/mailer.ts (2)

111-111: Inconsistent environment variable usage

While line 14 uses the new pattern env.USESEND_API_KEY ?? env.UNSEND_API_KEY, line 111 only checks for env.UNSEND_API_KEY. This could cause issues if only USESEND_API_KEY is set.

Apply this fix:

- } else if (env.UNSEND_API_KEY && env.FROM_EMAIL) {+ } else if ((env.USESEND_API_KEY || env.UNSEND_API_KEY) && env.FROM_EMAIL) {

31-31: Incomplete rebrand in user-facing content

The email subjects, text content, and log messages still reference "Unsend" instead of "UseSend". This creates brand inconsistency for users.

Apply these changes for consistent branding:

- const subject = "Sign in to Unsend";+ const subject = "Sign in to UseSend";- const text = `Hey,\n\nYou can sign in to Unsend by clicking the below URL:\n${url}\n\nYou can also use this OTP: ${token}\n\nThanks,\nUnsend Team`;+ const text = `Hey,\n\nYou can sign in to UseSend by clicking the below URL:\n${url}\n\nYou can also use this OTP: ${token}\n\nThanks,\nUseSend Team`;- const subject = "You have been invited to join Unsend";+ const subject = "You have been invited to join UseSend";- const text = `Hey,\n\nYou have been invited to join the team ${teamName} on Unsend.\n\nYou can accept the invitation by clicking the below URL:\n${url}\n\nThanks,\nUnsend Team`;+ const text = `Hey,\n\nYou have been invited to join the team ${teamName} on UseSend.\n\nYou can accept the invitation by clicking the below URL:\n${url}\n\nThanks,\nUseSend Team`;- logger.info("Email sent using unsend");+ logger.info("Email sent using UseSend");- "Error sending email using unsend, so fallback to resend",+ "Error sending email using UseSend, so fallback to resend",

Also applies to: 58-58, 41-41, 67-67, 121-121, 126-126

packages/sdk/src/unsend.ts (1)

100-100: Use proper TypeScript typing instead of any

According to the coding guidelines, avoid using any in TypeScript. Use unknown or proper types instead.

- async put<T>(path: string, body: any) {+ async put<T>(path: string, body: unknown) {- async patch<T>(path: string, body: any) {+ async patch<T>(path: string, body: unknown) {

Also applies to: 110-110

🧹 Nitpick comments (15)
packages/typescript-config/package.json (1)

2-2: Package scope rename is consistent; clarify publish intent.
With "private": true, publishConfig.access is moot. If this is workspace-only, keeping it private is fine; if external consumers need it, flip "private": false and publish under the new scope.

Would you like me to prep a follow-up PR to either (a) remove publishConfig for clarity, or (b) make it publishable and add a release step?

apps/web/package.json (1)

85-89: Standardize ESLint major version across workspace. apps/marketing uses ESLint ^9.25.1 while web, email-editor, and ui use ^8.57.1; align all to a single major version to prevent plugin compatibility issues.

packages/sdk/package.json (1)

21-22: Dev config rebrand LGTM; tighten publish output. Optional: restrict published files to only dist for a leaner package.

 "types": "./dist/index.d.ts",
+ "files": ["dist"],
"scripts": {
apps/web/src/server/service/ses-hook-parser.ts (6)

55-57: Match SES header names case-insensitively and trim values.

SES/MTAs may vary casing; strict equality risks misses. Normalize header name and trim the value.

- const emailIdHeader = data.mail.headers.find(- (h) => h.name === "X-Usesend-Email-ID" || h.name === "X-Unsend-Email-ID",- );+ const emailIdHeader = data.mail.headers.find((h) => {+ const n = h.name?.toLowerCase();+ return n === "x-usesend-email-id" || n === "x-unsend-email-id";+ });+ const emailId = emailIdHeader?.value?.trim();- if (emailIdHeader?.value) {+ if (emailId) {
email = await db.email.findUnique({
where: {
- id: emailIdHeader.value,+ id: emailId,
},
});

124-136: Deduplicate recipients before suppression writes.

Avoid duplicate suppression inserts when email.to contains duplicates/aliases; reduces DB churn.

- const recipientEmails = Array.isArray(email.to) ? email.to : [email.to];+ const recipientEmails = Array.isArray(email.to) ? email.to : [email.to];+ const uniqueRecipients = [...new Set(recipientEmails.map((e) => e?.trim().toLowerCase()))];
@@
- await Promise.all(- recipientEmails.map((recipientEmail) =>+ await Promise.all(+ uniqueRecipients.map((recipientEmail) =>
SuppressionService.addSuppression({

147-154: Log full error context for suppression failures.

Include stack to aid triage; message-only can hide root causes.

- logger.error(- {+ logger.error(+ {
emailId: email.id,
- recipients: recipientEmails,- error: error instanceof Error ? error.message : "Unknown error",+ recipients: recipientEmails,+ error: error instanceof Error ? { message: error.message, stack: error.stack } : { message: "Unknown error" },
},
"Failed to add emails to suppression list",
);

251-256: Guard analytics against duplicates at the DB level.

You already skip when an identical emailEvent exists; consider a unique index on (emailId, status) to enforce idempotency across retries.


396-400: Make worker concurrency configurable.

Tune via env per environment; keep 50 as default.

 private static worker = new Worker(
@@
- {- connection: getRedis(),- concurrency: 50,- },+ {+ connection: getRedis(),+ concurrency: Number(env.SES_WEBHOOK_CONCURRENCY ?? 50),+ },

412-416: Deduplicate jobs with jobId and keep default options.

Use jobId = messageId to avoid duplicate enqueues for the same SES event.

- return await this.sesHookQueue.add(- data.messageId,- data.event,- DEFAULT_QUEUE_OPTIONS,- );+ return await this.sesHookQueue.add(+ data.messageId,+ data.event,+ { jobId: data.messageId, ...DEFAULT_QUEUE_OPTIONS },+ );
packages/sdk/index.ts (1)

1-2: Primary export switch to UseSend is good; add explicit deprecation JSDoc for alias.

Surface TS deprecation warnings to consumers.

 export { UseSend } from "./src/unsend";
-export { UseSend as Unsend } from "./src/unsend"; // deprecated alias+/**+ * @deprecated Use `UseSend` instead. The `Unsend` alias will be removed in the next major release.+ */+export { UseSend as Unsend } from "./src/unsend";
apps/web/src/server/mailer.ts (1)

130-130: Update error message for consistency

The error message should reflect the new environment variable name for clarity.

- throw new Error("UNSEND_API_KEY not found");+ throw new Error("USESEND_API_KEY or UNSEND_API_KEY not found");
packages/sdk/src/unsend.ts (1)

9-10: Consider renaming internal function for consistency

The function isUNSENDErrorResponse still uses the old naming convention. Consider updating it for consistency with the rebrand.

-function isUNSENDErrorResponse(error: { error: ErrorResponse }) {+function isUseSendErrorResponse(error: { error: ErrorResponse }) {
return error.error.code !== undefined;
}

Also update the usage on line 60:

- if (isUNSENDErrorResponse(resp)) {+ if (isUseSendErrorResponse(resp)) {
apps/web/src/env.js (2)

36-37: New USESEND_API_KEY wiring looks correct; add a prod safeguard to ensure at least one API key is set.
Current schema keeps both keys optional, relying on downstream fallback. In production, fail fast if neither key is present.

Apply this minimal guard at the end of the file:

@@
export const env = createEnv({
...
});
+// Ensure at least one API key in production+if (+ process.env.NODE_ENV === "production" &&+ !process.env.USESEND_API_KEY &&+ !process.env.UNSEND_API_KEY+) {+ throw new Error("Set USESEND_API_KEY or UNSEND_API_KEY in production.");+}

Also applies to: 90-91


60-62: SMTP defaults updated — verify infra and consider adding port/TLS defaults.
Confirm the new host/user exist in all environments. Optionally add explicit SMTP_PORT and SMTP_SECURE defaults for clarity.

Example:

 SMTP_HOST: z.string().default("smtp.usesend.com"),
SMTP_USER: z.string().default("usesend"),
+ SMTP_PORT: z.string().default("587"),+ SMTP_SECURE: z+ .enum(["true", "false"])+ .default("true")+ .transform((s) => s === "true"),
packages/email-editor/package.json (1)

2-2: Package rename to @usesend/email-editor — OK.
Also update the description to reflect UseSend branding.

Apply:

- "description": "Email editor used by unsend",+ "description": "Email editor used by UseSend",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b1a59d2 and e03f821.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (29)
  • .eslintrc.js (1 hunks)
  • apps/marketing/.eslintrc.cjs (1 hunks)
  • apps/marketing/package.json (2 hunks)
  • apps/marketing/tsconfig.json (1 hunks)
  • apps/web/.eslintrc.cjs (1 hunks)
  • apps/web/package.json (4 hunks)
  • apps/web/src/env.js (4 hunks)
  • apps/web/src/lib/constants/example-codes.ts (3 hunks)
  • apps/web/src/server/aws/ses.ts (7 hunks)
  • apps/web/src/server/mailer.ts (4 hunks)
  • apps/web/src/server/service/ses-hook-parser.ts (9 hunks)
  • apps/web/tsconfig.json (1 hunks)
  • package.json (3 hunks)
  • packages/email-editor/.eslintrc.cjs (1 hunks)
  • packages/email-editor/package.json (4 hunks)
  • packages/email-editor/tsconfig.lint.json (1 hunks)
  • packages/eslint-config/package.json (1 hunks)
  • packages/sdk/.eslintrc.cjs (1 hunks)
  • packages/sdk/index.ts (1 hunks)
  • packages/sdk/package.json (3 hunks)
  • packages/sdk/src/unsend.ts (3 hunks)
  • packages/sdk/tsconfig.json (1 hunks)
  • packages/tailwind-config/package.json (2 hunks)
  • packages/typescript-config/package.json (1 hunks)
  • packages/ui/.eslintrc.cjs (1 hunks)
  • packages/ui/package.json (3 hunks)
  • packages/ui/tsconfig.lint.json (1 hunks)
  • rebrand.md (1 hunks)
  • tsconfig.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
{apps,packages}/**/*.{js,jsx,ts,tsx,css,scss,md,mdx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier with the Tailwind plugin for code formatting

Files:

  • packages/sdk/index.ts
  • apps/web/src/server/mailer.ts
  • apps/web/src/server/service/ses-hook-parser.ts
  • apps/web/src/server/aws/ses.ts
  • apps/web/src/lib/constants/example-codes.ts
  • apps/web/src/env.js
  • packages/sdk/src/unsend.ts
{apps,packages}/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{js,jsx,ts,tsx}: Group imports by source (internal/external) and alphabetize them
Use camelCase for variables and functions, PascalCase for components and classes
Use try/catch with specific error types for error handling

Files:

  • packages/sdk/index.ts
  • apps/web/src/server/mailer.ts
  • apps/web/src/server/service/ses-hook-parser.ts
  • apps/web/src/server/aws/ses.ts
  • apps/web/src/lib/constants/example-codes.ts
  • apps/web/src/env.js
  • packages/sdk/src/unsend.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{ts,tsx}: Use strong typing in TypeScript, avoid any, and use Zod for validation
Follow Vercel style guides with strict TypeScript

Files:

  • packages/sdk/index.ts
  • apps/web/src/server/mailer.ts
  • apps/web/src/server/service/ses-hook-parser.ts
  • apps/web/src/server/aws/ses.ts
  • apps/web/src/lib/constants/example-codes.ts
  • packages/sdk/src/unsend.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Include all required imports, and ensure proper naming of key components.

Files:

  • packages/sdk/index.ts
  • apps/web/src/server/mailer.ts
  • apps/web/src/server/service/ses-hook-parser.ts
  • apps/web/src/server/aws/ses.ts
  • apps/web/src/lib/constants/example-codes.ts
  • apps/web/src/env.js
  • packages/sdk/src/unsend.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation and semicolons (enforced by Prettier) in TypeScript files

Files:

  • packages/sdk/index.ts
  • apps/web/src/server/mailer.ts
  • apps/web/src/server/service/ses-hook-parser.ts
  • apps/web/src/server/aws/ses.ts
  • apps/web/src/lib/constants/example-codes.ts
  • packages/sdk/src/unsend.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use tRPC for internal API endpoints

In the web app, use the / alias for src imports (e.g., import { x } from "/utils/x")

Files:

  • apps/web/src/server/mailer.ts
  • apps/web/src/server/service/ses-hook-parser.ts
  • apps/web/src/server/aws/ses.ts
  • apps/web/src/lib/constants/example-codes.ts
**/*.{js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer TypeScript over JavaScript; new source files should use .ts/.tsx instead of .js/.jsx

Files:

  • apps/web/src/env.js
🧠 Learnings (4)
📚 Learning: 2025-08-09T10:37:58.146Z
Learnt from: CR
PR: unsend-dev/unsend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-09T10:37:58.146Z
Learning: Applies to {apps,packages}/**/*.{ts,tsx} : Follow Vercel style guides with strict TypeScript

Applied to files:

  • apps/marketing/tsconfig.json
  • apps/web/tsconfig.json
  • apps/marketing/package.json
📚 Learning: 2025-08-31T11:01:50.038Z
Learnt from: CR
PR: unsend-dev/unsend#0
File: AGENTS.md:0-0
Timestamp: 2025-08-31T11:01:50.038Z
Learning: Applies to {apps/web,apps/marketing,packages/ui,packages/email-editor}/**/*.tsx : Name React component files in PascalCase (e.g., AppSideBar.tsx)

Applied to files:

  • apps/marketing/tsconfig.json
📚 Learning: 2025-08-09T10:37:58.146Z
Learnt from: CR
PR: unsend-dev/unsend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-09T10:37:58.146Z
Learning: Applies to {apps,packages}/**/*.{js,jsx,ts,tsx,css,scss,md,mdx} : Use Prettier with the Tailwind plugin for code formatting

Applied to files:

  • apps/web/package.json
  • packages/email-editor/package.json
  • packages/ui/package.json
  • apps/marketing/package.json
  • packages/tailwind-config/package.json
📚 Learning: 2025-08-09T10:37:58.146Z
Learnt from: CR
PR: unsend-dev/unsend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-09T10:37:58.146Z
Learning: Applies to apps/smtp-server/**/*.{ts,tsx} : Use Hono for public API endpoints

Applied to files:

  • apps/web/src/server/mailer.ts
  • apps/web/src/env.js
🧬 Code graph analysis (4)
apps/web/src/server/mailer.ts (3)
packages/sdk/index.ts (2)
  • UseSend (1-1)
  • UseSend (2-2)
packages/sdk/src/unsend.ts (1)
  • UseSend (13-129)
apps/web/src/env.js (2)
  • env (5-125)
  • env (5-125)
apps/web/src/server/service/ses-hook-parser.ts (2)
apps/web/src/server/redis.ts (1)
  • getRedis (6-13)
apps/web/src/server/queue/queue-constants.ts (1)
  • DEFAULT_QUEUE_OPTIONS (4-9)
apps/web/src/server/aws/ses.ts (1)
apps/web/src/server/logger/log.ts (1)
  • logger (31-63)
packages/sdk/src/unsend.ts (2)
packages/sdk/types/index.ts (1)
  • ErrorResponse (1-4)
packages/sdk/index.ts (2)
  • UseSend (1-1)
  • UseSend (2-2)
🪛 LanguageTool
rebrand.md

[grammar] ~5-~5: There might be a mistake here.
Context: ...aming Decisions (confirm before changes) - Product name: UseSend (product), usesend...

(QB_NEW_EN)


[grammar] ~6-~6: There might be a mistake here.
Context: ...product), usesend (lowercase identifier) - Package scope: @usesend/* - SDK packag...

(QB_NEW_EN)


[grammar] ~7-~7: There might be a mistake here.
Context: ...(lowercase identifier) - Package scope: @usesend/* - SDK package name: usesend (replaces un...

(QB_NEW_EN)


[grammar] ~8-~8: There might be a mistake here.
Context: ...: usesend (replaces unscoped unsend) - Domains: usesend.com, `app.usesend.com...

(QB_NEW_EN)


[grammar] ~9-~9: There might be a mistake here.
Context: ... app.usesend.com, docs.usesend.com, smtp.usesend.com - Emails: hello@usesend.com (and other c...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ...esend.com(and other contact addresses) - HTTP headers:X-Usesend-*` (case-conser...

(QB_NEW_EN)


[grammar] ~11-~11: There might be a mistake here.
Context: ...onservative; accept legacy X-Unsend-*) - Env var prefix: USESEND_* (keep `UNSEN...

(QB_NEW_EN)


[grammar] ~12-~12: There might be a mistake here.
Context: ...eep UNSEND_* for a deprecation window) - Docker images: usesend/usesend, `usese...

(QB_NEW_EN)


[grammar] ~13-~13: There might be a mistake here.
Context: ...usesend/usesend, usesend/smtp-proxy, ghcr.io/usesend-dev/*- GitHub: org/repo rename tousesend-dev/...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ...de Changes ### 2.1 Workspace and config - Root package.json: - name: unsend ...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...kspace and config - Root package.json: - name: unsendusesend - scripts r...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...ot package.json: - name: unsendusesend - scripts referencing @unsend/* filters ...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...ripts referencing @unsend/* filters → @usesend/* - Root tsconfig.json: extends → `@uses...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: .../- Roottsconfig.json: extends@usesend/typescript-config/- Root ESLint config.eslintrc.js: @UNS...

(QB_NEW_EN)


[grammar] ~25-~25: There might be a mistake here.
Context: ...lintrc.js: @unsend/eslint-config/@usesend/eslint-config/-pnpm-lock.yaml`: will update after installs; do not edi...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...on: rename to @usesend/eslint-config. - packages/typescript-config/package.json: rename to @usesend/typescript-config`...

(QB_NEW_EN)


[grammar] ~33-~33: There might be a mistake here.
Context: ... to @usesend/ui. - Update all imports to new scope: - @unsend/ui → `@usesend...

(QB_NEW_EN)


[grammar] ~44-~44: There might be a mistake here.
Context: ...esend-Email-ID` (and accept/read legacy on server side). - Create backward-compat ...

(QB_NEW_EN)


[grammar] ~44-~44: There might be a mistake here.
Context: ...(and accept/read legacy on server side). - Create backward-compat entry points: -...

(QB_NEW_EN)


[grammar] ~45-~45: There might be a mistake here.
Context: .... - Create backward-compat entry points: - Temporary alias export `export { UseSend...

(QB_NEW_EN)


[grammar] ~46-~46: There might be a mistake here.
Context: ... with deprecation notice in docs/README. - Optionally publish a final unsend pack...

(QB_NEW_EN)


[grammar] ~49-~49: There might be a mistake here.
Context: ...4 Email Editor (packages/email-editor) - Update any copy (“Unsend is the best…”) ...

(QB_NEW_EN)


[grammar] ~59-~59: There might be a mistake here.
Context: ...layout.tsx(Metadata title/description) - Components withalt="Unsend"(e.g.,F...

(QB_NEW_EN)


[grammar] ~60-~60: There might be a mistake here.
Context: ...FullScreenLoading.tsx, email templates) - Sidebar branding (AppSideBar.tsx`) - De...

(QB_NEW_EN)


[grammar] ~61-~61: There might be a mistake here.
Context: ... - Sidebar branding (AppSideBar.tsx) - Default external URLs (docs, site, API e...

(QB_NEW_EN)


[grammar] ~62-~62: There might be a mistake here.
Context: ...use usesend.com and app.usesend.com. - Env validation (src/env.js): - Add `...

(QB_NEW_EN)


[grammar] ~66-~66: There might be a mistake here.
Context: ...c API (src/server/public-api/hono.ts): - “Unsend API” → “UseSend API”. - Error ...

(QB_NEW_EN)


[grammar] ~70-~70: There might be a mistake here.
Context: ...ID when sending (keep both in interim). - Hook parser (ses-hook-parser.ts`): acce...

(QB_NEW_EN)


[grammar] ~84-~84: There might be a mistake here.
Context: ...to unsend!” etc. → “Welcome to UseSend!”. - Examples + email addresses: `hello@usese...

(QB_NEW_EN)


[grammar] ~93-~93: There might be a mistake here.
Context: ...ate X/Twitter and GitHub org if changed. - MDX content: - All Unsend → UseSend, d...

(QB_NEW_EN)


[grammar] ~96-~96: There might be a mistake here.
Context: ...cker image names, compose service names. - SMTP guide: UNSEND_BASE_URL → `USESEND...

(QB_NEW_EN)


[grammar] ~109-~109: There might be a mistake here.
Context: ...P list if needed (unsendusesend). - Issue templates (`.github/ISSUE_TEMPLATE...

(QB_NEW_EN)


[grammar] ~114-~114: There might be a mistake here.
Context: ...nv.exampleand.env.selfhost.example: - Database defaults unsendusesend`. ...

(QB_NEW_EN)


[grammar] ~118-~118: There might be a mistake here.
Context: ... user usesend. - turbo.json env set: - Add USESEND_API_KEY; keep `UNSEND_API_...

(QB_NEW_EN)


[grammar] ~133-~133: There might be a mistake here.
Context: ...ocker, Stars) pointing to new repos/org. - Links to website/docs (usesend.com, `d...

(QB_NEW_EN)


[grammar] ~147-~147: There might be a mistake here.
Context: ...er-side for an interim period. - OpenAPI - Titles/descriptions to UseSend; server U...

(QB_NEW_EN)


[grammar] ~159-~159: There might be a mistake here.
Context: ...nor release; outbound prefer new header. - Docker - For N releases, push multi-ta...

(QB_NEW_EN)


[grammar] ~164-~164: There might be a mistake here.
Context: ...vdocs.usesend.com`. - Communication - Add a “Rebrand and migration guide” to d...

(QB_NEW_EN)


[grammar] ~170-~170: There might be a mistake here.
Context: ... - pnpm i, pnpm lint, pnpm build across monorepo. - Local run - pnpm dx to ...

(QB_NEW_EN)


[grammar] ~170-~170: There might be a mistake here.
Context: ...npm lint, pnpm buildacross monorepo. - Local run -pnpm dx` to boot dev infr...

(QB_NEW_EN)


[grammar] ~174-~174: There might be a mistake here.
Context: ...resent on outbound, parser accepts both. - SMTP proxy - Run the SMTP proxy and se...

(QB_NEW_EN)


[grammar] ~178-~178: There might be a mistake here.
Context: ...nd` alias still works during transition. - Marketing & Docs - Build marketing and...

(QB_NEW_EN)


[grammar] ~179-~179: There might be a mistake here.
Context: ...ks during transition. - Marketing & Docs - Build marketing and docs; visually verif...

(QB_NEW_EN)


[grammar] ~184-~184: There might be a mistake here.
Context: ...nsend” appears and likely needs updates: - Root - package.json (name, scripts) ...

(QB_NEW_EN)


[grammar] ~186-~186: There might be a mistake here.
Context: ...me, scripts) and tsconfig.json extend. - Packages - packages/*/package.json n...

(QB_NEW_EN)


[grammar] ~188-~188: There might be a mistake here.
Context: ... names and internal deps (@unsend/*). - packages/sdk/src/unsend.ts, email.ts, contact.ts` (class name, ...

(QB_NEW_EN)


[grammar] ~191-~191: There might be a mistake here.
Context: ...ckages/ui/* imports and package naming. - Web app (apps/web) - src/app/layout...

(QB_NEW_EN)


[grammar] ~193-~193: There might be a mistake here.
Context: ...ppSideBar.tsx; FullScreenLoading.tsx. - Email templates under src/server/email-...

(QB_NEW_EN)


[grammar] ~197-~197: There might be a mistake here.
Context: ... - src/env.js env names and defaults. - Public assets under apps/web/public/* ...

(QB_NEW_EN)


[grammar] ~200-~200: There might be a mistake here.
Context: ...docker-compose.yml image and env names. - Marketing (apps/marketing) - src/ap...

(QB_NEW_EN)


[grammar] ~202-~202: There might be a mistake here.
Context: ...ontact emails; imports from @unsend/*. - Public assets in `apps/marketing/public/...

(QB_NEW_EN)


[grammar] ~205-~205: There might be a mistake here.
Context: ...sdk/*(env names, domains, repo links). - Docker & infra -docker/dev/compose.y...

(QB_NEW_EN)


[grammar] ~208-~208: There might be a mistake here.
Context: ...ocker/README.mdproduct name and links. - GitHub -.github/workflows/publish.ym...

(QB_NEW_EN)


[grammar] ~210-~210: There might be a mistake here.
Context: ...EMPLATEreferences toapp.unsend.dev`. - Misc - README.md badges/links; CONTRIB...

(QB_NEW_EN)


[grammar] ~216-~216: There might be a mistake here.
Context: ... ## 11) Release & Rollout - Versioning - Bump versions where appropriate; SDK lik...

(QB_NEW_EN)


[grammar] ~217-~217: There might be a mistake here.
Context: ...appropriate; SDK likely a major version. - Changelog - Add a “Rebrand to UseSend”...

(QB_NEW_EN)


[grammar] ~220-~220: There might be a mistake here.
Context: ...s, package names, domains). - Monitoring - Watch error logs for missing env vars or...

(QB_NEW_EN)


[grammar] ~227-~227: There might be a mistake here.
Context: .... - Stop pushing unsend/* docker tags. - Remove alias exports for Unsend in SDK...

(QB_NEW_EN)


[grammar] ~228-~228: There might be a mistake here.
Context: ...emove alias exports for Unsend in SDK and old unsend npm package. - Cleanup CSS...

(QB_NEW_EN)


[grammar] ~228-~228: There might be a mistake here.
Context: ...ndin SDK and oldunsend npm package. - Cleanup CSS dual selectors (.unsend-*`)...

(QB_NEW_EN)

🔇 Additional comments (23)
tsconfig.json (1)

2-2: Approve TS config extension
Verified packages/typescript-config/base.json is present.

packages/sdk/package.json (1)

2-2: Deprecated alias present – no action needed.
The export { UseSend as Unsend } from "./src/unsend"; line already exists in packages/sdk/index.ts, so the legacy import alias is in place.

rebrand.md (1)

1-237: Comprehensive rebrand documentation with clear migration strategy.

This thorough rebranding plan covers all critical aspects including naming conventions, code changes, infrastructure updates, and migration strategy with backward compatibility. The structured approach with verification steps will help ensure a successful transition from Unsend to UseSend.

package.json (3)

2-2: LGTM! Root package name updated for rebrand.

The package name change from "unsend" to "usesend" aligns with the project-wide rebranding effort.


6-8: LGTM! Build scripts updated to use new package scope.

All build scripts have been properly updated to reference @usesend/email-editor instead of the old @unsend/email-editor scope.

Also applies to: 10-10


31-32: LGTM! DevDependencies updated for rebrand.

The ESLint and TypeScript configuration packages have been updated from @unsend/* to @usesend/* scope, maintaining workspace references.

packages/ui/.eslintrc.cjs (1)

4-4: LGTM! ESLint config updated for rebrand.

The extends configuration has been properly updated from @unsend/eslint-config/react-internal.js to @usesend/eslint-config/react-internal.js, maintaining consistency with the rebranding effort.

packages/ui/tsconfig.lint.json (1)

2-2: Update leftover @unsend/typescript-config extends references to @usesend/typescript-config
– packages/ui/tsconfig.json (line 2): "extends": "@unsend/typescript-config/react-library.json""extends": "@usesend/typescript-config/react-library.json"
– packages/tailwind-config/tsconfig.json (line 2): "extends": "@unsend/typescript-config/base.json""extends": "@usesend/typescript-config/base.json"
– packages/email-editor/tsconfig.json (line 2): "extends": "@unsend/typescript-config/react-library.json""extends": "@usesend/typescript-config/react-library.json"
Ensure the updated package is installed/linked and the workspace resolves @usesend/typescript-config.

⛔ Skipped due to learnings
Learnt from: CR
PR: unsend-dev/unsend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-09T10:37:58.146Z
Learning: Applies to {apps,packages}/**/*.{ts,tsx} : Follow Vercel style guides with strict TypeScript
packages/email-editor/tsconfig.lint.json (1)

2-2: Confirmed shared config exposes react-library.json. The file exists at packages/typescript-config/react-library.json, so the "extends" path is valid.

apps/web/.eslintrc.cjs (1)

4-4: Approve ESLint config update
All apps/packages now depend on @usesend/eslint-config and no .eslintrc* extend @unsend configs.

apps/marketing/.eslintrc.cjs (1)

4-4: No remaining @unsend/eslint-config extends found
Ran rg -nS '@unsend/eslint-config' -g '**/.eslintrc.*', no matches.

packages/eslint-config/package.json (1)

2-2: Rename verified—consumers updated & versions exist.
All .eslintrc.* extend @usesend/eslint-config/* with no stale @unsend references, and every declared devDependency range resolves on npm.

Consider moving ESLint/TS entries to peerDependencies to prevent duplicate installs in dependents.

packages/email-editor/.eslintrc.cjs (1)

4-4: Verify ESLint config parity after extends rebrand

Extends path update to @usesend/eslint-config/react-internal.js is correct; run:

pnpm --filter @usesend/email-editor exec eslint --print-config src/index.ts > /tmp/eslint-usesend.json

and compare the output against the previous react-internal.js config.

packages/sdk/.eslintrc.cjs (1)

4-4: Extends path rebrand is fine; monorepo uses a single workspace:* version.

packages/sdk/index.ts (1)

1-2: No default Unsend imports detected; default export alias unnecessary
All code and documentation use named imports (import { Unsend } …); no default import Unsend from "unsend" occurrences were found.

apps/web/src/server/aws/ses.ts (2)

236-238: Good backward compatibility with dual headers

Setting both X-Usesend-Email-ID and X-Unsend-Email-ID headers ensures backward compatibility during the rebrand transition. This allows existing systems to continue working while new systems adopt the new header name.


85-141: Good formatting improvements

The addition of trailing commas improves code maintainability and reduces diff noise in future changes.

apps/web/src/server/mailer.ts (1)

14-14: Good backward compatibility for API key

Using env.USESEND_API_KEY ?? env.UNSEND_API_KEY provides smooth migration support during the rebrand, allowing both old and new environment variables to work.

packages/sdk/src/unsend.ts (2)

5-7: Well-implemented backward compatibility

The base URL resolution with fallback chain (USESEND_BASE_URL → UNSEND_BASE_URL → default) ensures smooth migration during the rebrand.


27-27: Good API key fallback implementation

The fallback from USESEND_API_KEY to UNSEND_API_KEY maintains backward compatibility for existing users.

apps/web/src/env.js (2)

16-17: Trailing comma and error string formatting — OK.
No semantic change; safe.


30-31: NEXTAUTH_URL validator formatting — OK.
No behavior change; remains correct for Vercel.

packages/email-editor/package.json (1)

23-26: Switched internal presets to @usesend/ — OK.*
Consistent with the rebrand.

Comment thread.eslintrc.js
module.exports = {
ignorePatterns: ["apps/**", "packages/**"],
extends: ["@unsend/eslint-config/library.js"],
extends: ["@usesend/eslint-config/library.js"],

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.

💡 Verification agent

🧩 Analysis chain

Rebrand extends path looks good; verify package availability.

Ensure @usesend/eslint-config is published/installed and version-pinned consistently across the monorepo.

Run to confirm install/lock:


🏁 Script executed:

#!/bin/bash
pnpm -s ls @usesend/eslint-config || npm ls @usesend/eslint-config

Length of output: 449


Pin @usesend/eslint-config to a released version
The workspace dependency is currently "workspace:*" and triggers npm ELSPROBLEMS; update all package.json entries (root and sub-packages) to the actual semver of @usesend/eslint-config (after publishing to the registry) to ensure consistent installs.

🤖 Prompt for AI Agents
.eslintrc.js around line 5: the project currently references the workspace
version of @usesend/eslint-config which causes npm ELSPROBLEMS; after publishing
the package to the registry, update all package.json entries (root and every
sub-package) to replace "workspace:*" with the published semver (e.g. "1.2.3"),
run npm install to refresh node_modules and update lockfile, and search the repo
for any remaining "workspace:*" references to ensure all instances are pinned to
the released version.

Comment threadapps/marketing/package.json Outdated
Comment on lines +13 to +14
"@usesend/email-editor": "workspace:*",
"@usesend/ui": "workspace:*",

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.

💡 Verification agent

🧩 Analysis chain

Rebrand deps/devDeps look consistent.

Workspace scope switch to @usesend is correct here. Consider verifying there are no lingering @unsend imports in this app.

Also applies to: 26-27


🏁 Script executed:

#!/bin/bash
rg -nP --type=ts --type=tsx -g 'apps/marketing/**''@unsend/|from\s+[\'"]unsend[\'"]'

Length of output: 116


🏁 Script executed:

#!/bin/bash
rg -nP --type=ts --type=tsx -g 'apps/marketing/**' -e '@unsend/' -e "from\s+['\"]unsend['\"]"

Length of output: 125


🏁 Script executed:

#!/bin/bash# Search for any lingering @unsend imports in TypeScript/JavaScript files
rg -nP -g 'apps/marketing/**/*.{ts,tsx,js,jsx}' -e '@unsend/' -e "from\s+['\"]unsend['\"]"

Length of output: 1116


Update lingering @unsend imports in the marketing app
All files in apps/marketing still import from @unsend/*; these must be switched to the new @usesend/* packages to match the package.json changes. Specifically:

  • src/components/ui/styled-input.tsx & background-beams.tsx
  • src/app/layout.tsx (CSS, ThemeProvider, TextWithCopyButton, Separator)
  • src/app/editor/page.tsx (Editor, Button)
  • src/app/IntegrationCode.tsx (Code)
  • tailwind.config.ts (sharedConfig import & UI path glob)
🤖 Prompt for AI Agents
In apps/marketing/package.json around lines 13-14 and across the listed files
(src/components/ui/styled-input.tsx, src/components/ui/background-beams.tsx,
src/app/layout.tsx, src/app/editor/page.tsx, src/app/IntegrationCode.tsx,
tailwind.config.ts), update all imports that reference the old @unsend/* scope
to the new @usesend/* scope to match package.json; specifically replace imports
for UI, email-editor, CSS/ThemeProvider/TextWithCopyButton/Separator,
Editor/Button, and Code to use @usesend/* equivalents, and update
tailwind.config.ts to import sharedConfig from the @usesend path and update the
UI path glob to point to @usesend/ui files. Ensure import specifiers and
relative paths remain correct and run TypeScript build to confirm no unresolved
imports remain.

@@ -1,5 +1,5 @@
{
"extends": "@unsend/typescript-config/nextjs.json",
"extends": "@usesend/typescript-config/nextjs.json",

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.

💡 Verification agent

🧩 Analysis chain

Extends path rebrand looks good; verify workspace resolution.
Ensure @usesend/typescript-config is present in the workspace (root or app devDeps) and that no @unsend/typescript-config extends remain.

Run:


🏁 Script executed:

#!/bin/bash# Find any lingering @unsend extends
rg -nP '"extends"\s*:\s*"@unsend/typescript-config' -g '!**/node_modules/**' -S
# Confirm the new package is referenced somewhere (root or app-level)
rg -nP '"@usesend/typescript-config"\s*:' -g '**/package.json' -S

Length of output: 811


Replace remaining @unsend extends
In packages/ui/tsconfig.json, packages/tailwind-config/tsconfig.json, and packages/email-editor/tsconfig.json, update

"extends": "@unsend/typescript-config/…json"

to

"extends": "@usesend/typescript-config/…json"

and ensure each package’s devDependencies include @usesend/typescript-config.

🤖 Prompt for AI Agents
In apps/marketing/tsconfig.json around line 2 (and in packages/ui/tsconfig.json,
packages/tailwind-config/tsconfig.json, and
packages/email-editor/tsconfig.json), the "extends" reference still points to
"@unsend/typescript-config/…json"; update each to "extends":
"@usesend/typescript-config/…json" to use the new package namespace, and then
add "@usesend/typescript-config" to each package's devDependencies in its
package.json (remove or keep old @unsend entry as appropriate) so the extended
config is available during install/build.

Comment threadapps/web/package.json
Comment on lines +37 to +38
"@usesend/email-editor": "workspace:*",
"@usesend/ui": "workspace:*",

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.

💡 Verification agent

🧩 Analysis chain

Rebrand: UI/email-editor deps updated.

Looks good; align imports to @usesend/* in code.


🏁 Script executed:

#!/bin/bash
rg -nP --type=ts --type=tsx -g 'apps/web/**''@unsend/'

Length of output: 83


🏁 Script executed:

#!/bin/bash# Search for any remaining '@unsend/' imports in ts/tsx within apps/web
rg -n '@unsend/' -g 'apps/web/**/*.ts' -g 'apps/web/**/*.tsx'

Length of output: 28196


Replace all @unsend/* imports with @usesend/*.
Numerous files under apps/web (e.g., tailwind.config.ts, ThemeSwitcher.tsx, server/service, and countless components) still reference @unsend/ui or @unsend/email-editor; update every import to @usesend/ui and @usesend/email-editor to align with package.json.

Comment threadapps/web/package.json
"tldts": "^7.0.4",
"ua-parser-js": "^2.0.3",
"unsend": "workspace:*",
"usesend": "workspace:*",

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.

💡 Verification agent

🧩 Analysis chain

SDK dep switched to "usesend": audit old import paths.

Make sure no code still imports "unsend".


🏁 Script executed:

#!/bin/bash
rg -nP --type=ts --type=tsx -g 'apps/web/**''\bfrom\s+[\'"]unsend[\'"]'

Length of output: 116


🏁 Script executed:

#!/bin/bash
rg -nP --type ts --type tsx -g 'apps/web/**'"from ['\"]unsend['\"]"

Length of output: 102


🏁 Script executed:

#!/bin/bash
rg -nP -g 'apps/web/**/*.{ts,tsx}''\bfrom\s+["\']unsend["\']'

Length of output: 116


🏁 Script executed:

#!/bin/bash# Search for any occurrence of "unsend" in code files under apps/web
rg -n -g 'apps/web/**/*.{ts,tsx,js,jsx}''unsend'

Length of output: 33988


Migrate remaining unsend imports to usesend
apps/web still contains numerous imports referencing @unsend/* (e.g. tailwind.config.ts, src/server/mailer.ts, various UI and email-editor paths); update all to the corresponding usesend packages.

Comment threadpackages/sdk/package.json Outdated
@@ -1,5 +1,5 @@
{
"name": "unsend",
"name": "usesend",

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.

💡 Verification agent

🧩 Analysis chain

Package renamed to "usesend": verify npm name availability and import updates.

Before publishing, confirm the name isn’t already claimed and repo imports are updated.


🏁 Script executed:

#!/bin/bash# Check if package name exists on npm
curl -sf https://registry.npmjs.org/usesend >/dev/null &&echo"exists on npm"||echo"available on npm (404)"

Length of output: 100


Package name “usesend” is available on npm; update all imports to “usesend” before publishing.

🤖 Prompt for AI Agents
packages/sdk/package.json around line 2: the package "name" is currently set to
"usesend" is required for publishing; update the "name" field to "usesend" and
then update all internal and external module references to that package across
the repo (imports, require() calls, TS path aliases, package-lock / pnpm-lock,
workspaces config, CI/packaging scripts, READMEs and tests) so they reference
"usesend" consistently; run the package manager install and rebuild to ensure
lockfiles and consumers are updated and run tests to verify no import breaks
before publishing.

@@ -1,5 +1,5 @@
{
"extends": "@unsend/typescript-config/base.json",
"extends": "@usesend/typescript-config/base.json",

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.

💡 Verification agent

🧩 Analysis chain

SDK tsconfig extend updated; looks good.
No functional risk; just ensure no remaining @unsend/typescript-config extends in package tsconfigs.


🏁 Script executed:

#!/bin/bash
rg -nP '"extends"\s*:\s*"@unsend/typescript-config' -g 'packages/**/tsconfig*.json' -S

Length of output: 369


Replace all remaining @unsend/typescript-config extends
packages/ui/tsconfig.json (line 2), packages/tailwind-config/tsconfig.json (line 2), packages/email-editor/tsconfig.json (line 2) still reference @unsend/typescript-config/*; update each to @usesend/typescript-config/*.

🤖 Prompt for AI Agents
In packages/sdk/tsconfig.json around line 2 the "extends" value incorrectly
references "@unsend/typescript-config/base.json"; update this to
"@usesend/typescript-config/base.json". Also open the other files mentioned
(packages/ui/tsconfig.json line 2, packages/tailwind-config/tsconfig.json line
2, packages/email-editor/tsconfig.json line 2) and make the same replacement
from "@unsend/typescript-config/*" to "@usesend/typescript-config/*" so all
tsconfig extends point to the correct package.

@@ -1,5 +1,5 @@
{
"name": "@unsend/tailwind-config",
"name": "@usesend/tailwind-config",

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.

💡 Verification agent

🧩 Analysis chain

Tailwind config package renamed; update all consumers.
Make sure downstream configs switch from @unsend/tailwind-config to @usesend/tailwind-config. Consider deprecating the old package name if it was ever published.


🏁 Script executed:

#!/bin/bash# Find old/new references across the repo
rg -nP '@unsend/tailwind-config|@usesend/tailwind-config' -g '!**/node_modules/**' -C2

Length of output: 4693


Replace outdated Tailwind imports in config files
Update all tailwind.config.ts files to import from the new package scope:

  • packages/ui/tailwind.config.ts (line 2)
  • packages/email-editor/tailwind.config.ts (line 2)
  • apps/web/tailwind.config.ts (line 2)
  • apps/marketing/tailwind.config.ts (line 2)

Change

importsharedConfigfrom"@unsend/tailwind-config/tailwind.config";

to

importsharedConfigfrom"@usesend/tailwind-config/tailwind.config";

Also consider deprecating the old @unsend/tailwind-config package if it was ever published.

@@ -1,5 +1,5 @@
{
"name": "@unsend/ui",
"name": "@usesend/ui",

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.

💡 Verification agent

🧩 Analysis chain

Package renamed to @usesend/ui.

LGTM; ensure all consumers updated.


🏁 Script executed:

#!/bin/bash
rg -nP '@unsend/ui|from\s+[\'"]@unsend/ui[\'"]'

Length of output: 116


🏁 Script executed:

#!/bin/bash# Search for any remaining references to the old package name "@unsend/ui"
rg -nP "@unsend/ui|from\s+['\"]@unsend/ui['\"]"

Length of output: 31017


Update all consumer imports from @unsend/ui → @usesend/ui
Replace every occurrence of @unsend/ui in import paths and configs (e.g. in packages/email-editor, apps/web, apps/marketing, Tailwind setup, etc.) with @usesend/ui.

🤖 Prompt for AI Agents
In packages/ui/package.json around line 2, the package name has been changed to
"@usesend/ui" but many consumer imports and config references still use the old
"@unsend/ui"; search the repository for all occurrences of "@unsend/ui" and
replace them with "@usesend/ui" (including import statements in
packages/email-editor, apps/web, apps/marketing, Tailwind config,
PostCSS/tailwind imports, tsconfig path aliases, package.json dependencies, and
any build or CI configs). After replacing, update any relative docs or README
mentions, run TypeScript build and the monorepo bootstrap (or yarn/nx/pnpm
install) to ensure no broken imports, and run tests/lint to confirm everything
resolves correctly.

Comment on lines +20 to 24
"@usesend/eslint-config": "workspace:*",
"@usesend/tailwind-config": "workspace:*",
"@usesend/typescript-config": "workspace:*",
"eslint": "^8.57.1",
"postcss": "^8.5.3",

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.

💡 Verification agent

🧩 Analysis chain

Align ESLint version and add React peer deps.

  • ESLint here is ^8 while other apps use ^9; standardize to a single major.
  • UI libs should declare react/react-dom as peerDependencies to avoid duplicate React in consumers.
 "devDependencies": {
- "eslint": "^8.57.1",+ "eslint": "^9.25.1",
"postcss": "^8.5.3",
@@
- "react": "19.1.0",
"tailwindcss": "^3.4.1",
"typescript": "^5.8.3"
},
+ "peerDependencies": {+ "react": "^18.2.0 || ^19.0.0",+ "react-dom": "^18.2.0 || ^19.0.0"+ },+ "peerDependenciesMeta": {+ "react-dom": { "optional": true }+ },

🏁 Script executed:

#!/bin/bash# Check if any consumer currently pulls a second copy of react due to missing peer deps
jq -r '.dependencies.react? // empty' apps/*/package.json packages/*/package.json 2>/dev/null | nl

Length of output: 417


🏁 Script executed:

#!/bin/bash# Verify ESLint versions across all workspaces and inspect packages/ui dependenciesecho">> ESLint versions in each workspace:"forpkgin apps/*/package.json packages/*/package.json;do
ver=$(jq -r '.devDependencies.eslint // .dependencies.eslint // empty'"$pkg"2>/dev/null)if [[ -n"$ver" ]];thenecho"$pkg: $ver"fidoneecho -e "\n>> packages/ui dependency declarations:"
jq '{ dependencies, devDependencies, peerDependencies }' packages/ui/package.json

Length of output: 3886


Align ESLint major version and add React peer dependencies in packages/ui/package.json

  • In devDependencies, bump "eslint" from ^8.57.1 to ^9.25.1.
  • Remove the "react": "19.1.0" entry from devDependencies.
  • Add the following to declare React as a peer dependency:
--- a/packages/ui/package.json+++ b/packages/ui/package.json
@@ devDependencies
- "eslint": "^8.57.1",+ "eslint": "^9.25.1",
@@ devDependencies
- "react": "19.1.0",
@@
},
+ "peerDependencies": {+ "react": "^18.2.0 || ^19.0.0",+ "react-dom": "^18.2.0 || ^19.0.0"+ },+ "peerDependenciesMeta": {+ "react-dom": { "optional": true }+ },

Ensure this aligns with the ESLint version chosen across workspaces and that consumers satisfy the React peer constraints.

📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@usesend/eslint-config": "workspace:*",
"@usesend/tailwind-config": "workspace:*",
"@usesend/typescript-config": "workspace:*",
"eslint": "^8.57.1",
"postcss": "^8.5.3",
"@usesend/eslint-config": "workspace:*",
"@usesend/tailwind-config": "workspace:*",
"@usesend/typescript-config": "workspace:*",
"eslint": "^9.25.1",
"postcss": "^8.5.3"
},
"peerDependencies": {
"react": "^18.2.0 || ^19.0.0",
"react-dom": "^18.2.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"react-dom": { "optional": true }
},
🤖 Prompt for AI Agents
In packages/ui/package.json around lines 20 to 24, bump the devDependency
"eslint" from "^8.57.1" to "^9.25.1", remove the "react": "19.1.0" entry from
devDependencies, and add React peer dependencies by adding a "peerDependencies"
section (or extending it) that declares at minimum "react" and "react-dom" (set
the version ranges to match workspace conventions, e.g. "^19.0.0" or the agreed
major used across workspaces) so consumers must provide React; ensure the eslint
version aligns with other workspaces.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Aug 31, 2025

Copy link
Copy Markdown

Deploying unsend with Cloudflare Pages Cloudflare Pages

Latest commit:128031f
Status:🚫 Build failed.

View logs

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 21

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (30)
README.md (2)

33-38: Tighten grammar and clarity in the intro paragraph

Fix articles, punctuation, spacing, and capitalization (WhatsApp). Also add missing “to”.

-As most of email products out there, useSend also uses Amazon SES under the hood to send emails. We provide an open and alternative way to send emails reliably and cheaply with a great dashboard. You can also use useSend manage contacts and send bulk emails(newsletter, product updates etc). We will take care of the subscriptions.+Like most email products, useSend uses Amazon SES under the hood to send emails. We provide an open, alternative way to send emails reliably and inexpensively with a great dashboard. You can also use useSend to manage contacts and send bulk emails (newsletters, product updates, etc.). We take care of subscriptions.-Currently we only support emails, but we plan to expand to other sending protocols like SMS, push notification and even whatsapp.+Currently, we only support email, but we plan to expand to other sending channels like SMS, push notifications, and even WhatsApp.-We are currently in beta and trying to rollout to public slowly. If you're interested [join our waitlist](https://app.youform.io/forms/caja89vr) or drop in a message in discord.+We are currently in beta and rolling out to the public gradually. If you're interested, [join our waitlist](https://app.youform.io/forms/caja89vr) or drop a message in Discord.

41-50: Update README feature list wording and statuses

  • Change “Marketing email” to “Marketing emails”
  • Mark “Webhook support” as implemented
  • Clarify “Bring your own AWS credentials (BYO)” phrasing
- [x] Marketing email+ [x] Marketing emails- [ ] Webhook support+ [x] Webhook support- [ ] BYO AWS credentials+ [ ] Bring your own AWS credentials (BYO)
CONTRIBUTION.md (2)

81-81: Typo: “local-sen-sns” → “local-ses-sns”.

SES/SNS spelling error in a user-facing note.

Apply this diff:

-> You can skip this by using the `local-sen-sns` image for local-only email development.+> You can skip this by using the `local-ses-sns` image for local-only email development.

144-155: Code structure still lists “marketing” app.

If the marketing app was removed, update the tree.

Suggested diff:

 apps/
├── web # Dashboard & Email Infra
-├── marketing # Landing page
├── docs # This documentation site
apps/web/src/app/(dashboard)/emails/email-details.tsx (3)

9-10: Client component importing Prisma at runtime — switch to type-only imports

This file is a client component ("use client"). Value imports from @prisma/client and its runtime can pull server-only code into the browser bundle. Import types only.

-import { EmailStatus } from "@prisma/client";-import { JsonValue } from "@prisma/client/runtime/library";+import type { EmailStatus } from "@prisma/client";+import type { JsonValue } from "@prisma/client/runtime/library";

96-103: Guard against undefined emailEvents and avoid crash

emailQuery.data?.emailEvents.map will throw when emailEvents is undefined. Tighten the condition and optional-chain the map.

-{emailQuery.data?.latestStatus !== "SCHEDULED" ? (+{emailQuery.data?.latestStatus !== "SCHEDULED" && !!emailQuery.data?.emailEvents?.length ? (
@@
- {emailQuery.data?.emailEvents.map((evt) => (+ {emailQuery.data?.emailEvents?.map((evt) => (

105-106: Use a stable unique key for events

key={evt.status} will collide across multiple events with the same status. Use an id (preferred) or a composite fallback.

- <div- key={evt.status}+ <div+ key={evt.id ?? `${evt.status}-${evt.createdAt}`}

If id isn’t available, pass the index in .map((evt, idx) => ...) and use ${...}-${idx}.

apps/web/src/app/(dashboard)/dashboard/reputation-metrics.tsx (2)

1-1: Add "use client" (hooks are used in this file).

This component calls React hooks (api...useQuery, useColors) and must be a Client Component in Next.js app router.

+"use client";+
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@usesend/ui/src/tooltip";

112-113: Guard toFixed calls — can throw when metrics is undefined.

metrics?.bounceRate.toFixed(2) and metrics?.complaintRate.toFixed(2) will crash when metrics is undefined because toFixed is invoked on undefined.

- {metrics?.bounceRate.toFixed(2)}%+ {(metrics?.bounceRate ?? 0).toFixed(2)}%
- {metrics?.complaintRate.toFixed(2)}%+ {(metrics?.complaintRate ?? 0).toFixed(2)}%

Also applies to: 258-259

apps/web/src/app/api/to-html/route.ts (1)

14-15: Rebrand placeholder and URL; keep backward compatibility.

Still uses the Unsend placeholder and domain. Map both the new and old placeholders to UseSend, so templates render regardless of migration timing.

Apply:

 linkValues: {
- "{{unsend_unsubscribe_url}}": "https://unsend.com/unsubscribe",+ "{{usesend_unsubscribe_url}}": "https://usesend.com/unsubscribe",+ "{{unsend_unsubscribe_url}}": "https://usesend.com/unsubscribe",
},
apps/web/src/server/service/email-service.ts (2)

584-587: Bug: templateCache keys use Number(templateId) → NaN collisions.

templateId is a string (e.g., UUID). Converting to Number yields NaN, collapsing the cache to a single entry. Use string keys and strengthen typing.

Apply:

- const templateCache = new Map<- number,- { subject: string; content: any; renderer: EmailRenderer }- >();+ const templateCache = new Map<+ string,+ { subject: string; content: unknown; renderer: EmailRenderer }+ >();
// ...
- if (templateId) {- let templateData = templateCache.get(Number(templateId));+ if (templateId) {+ let templateData = templateCache.get(templateId);
if (!templateData) {
const template = await db.template.findUnique({
where: { id: templateId },
});
if (template) {
const jsonContent = JSON.parse(template.content || "{}");
templateData = {
subject: template.subject || "",
content: jsonContent,
renderer: new EmailRenderer(jsonContent),
};
- templateCache.set(Number(templateId), templateData);+ templateCache.set(templateId, templateData);
}
}

Also applies to: 623-636, 650-651


36-45: Escape variable keys in regex replacement.

Unescaped keys (e.g., user.name) act as regex, causing incorrect replacements. Escape metacharacters.

Apply:

 export const replaceVariables = (
text: string,
variables: Record<string, string>,
) => {
return Object.keys(variables).reduce((accum, key) => {
- const re = new RegExp(`{{${key}}}`, "g");- const returnTxt = accum.replace(re, variables[key] as string);+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");+ const re = new RegExp(`{{${escapedKey}}}`, "g");+ const returnTxt = accum.replace(re, variables[key] ?? "");
return returnTxt;
}, text);
};
apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx (1)

49-60: Guard against divide-by-zero in percentage calculations.

If delivered is 0/undefined, UI will show NaN/Infinity. Set 0% when no deliveries.

Apply:

- percentage: (campaign.unsubscribed / campaign.delivered) * 100,+ percentage:+ campaign.delivered > 0+ ? (campaign.unsubscribed / campaign.delivered) * 100+ : 0,
...
- percentage: (campaign.clicked / campaign.delivered) * 100,+ percentage:+ campaign.delivered > 0+ ? (campaign.clicked / campaign.delivered) * 100+ : 0,
...
- percentage: (campaign.opened / campaign.delivered) * 100,+ percentage:+ campaign.delivered > 0+ ? (campaign.opened / campaign.delivered) * 100+ : 0,
packages/email-editor/src/extensions/ImageExtension.tsx (1)

124-133: Fix toast.error call signature.

Passing two args likely drops the error message. Use a single formatted string (safe across toast libs).

Apply:

- toast.error("Error uploading image:", error.message);+ toast.error(+ `Error uploading image: ${+ error instanceof Error ? error.message : String(error)+ }`+ );
apps/web/src/app/(dashboard)/settings/usage/usage.tsx (1)

50-53: Update brand copy: “unsend editor” → “UseSend editor”.

Aligns with PR rebrand objective.

- : "Mails designed sent from unsend editor"}+ : "Emails designed in the UseSend editor"}
apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx (2)

20-111: Remove unused code blocks with hardcoded token and PII.

jsCode, pythonCode, rubyCode, and phpCode are unused and contain a bearer-like token and real emails. Delete them to prevent accidental leakage and reduce bundle size.

Apply this diff:

-const jsCode = `const requestOptions = {- method: "POST",- headers: {- "Accept": "application/json",- "Content-Type": "application/json",- "Authorization": "Bearer us_ad9a79256e366399c747cbf0b38eca3c472e8a2e"- },- body: JSON.stringify({- "to": "koushikmohan1996@gmail.com",- "from": "hello@test.splitpro.app",- "subject": "Test mail",- "html": "<p>Hello this is a test mail</p>"- }),- redirect: "follow"-};--fetch("http://localhost:3000/api/v1/emails", requestOptions)- .then(response => response.text())- .then(result => console.log(result))- .catch(error => console.error(error));-`;--const pythonCode = `import requests-import json--url = "http://localhost:3000/api/v1/emails"--payload = json.dumps({- "to": "koushikmohan1996@gmail.com",- "from": "hello@test.splitpro.app",- "subject": "Test mail",- "html": "<p>Hello this is a test mail</p>"-})-headers = {- 'Accept': 'application/json',- 'Content-Type': 'application/json',- 'Authorization': 'Bearer us_ad9a79256e366399c747cbf0b38eca3c472e8a2e'-}--response = requests.request("POST", url, headers=headers, data=payload)--print(response.text)`;--const rubyCode = `require 'uri'-require 'net/http'-require 'json'--url = URI("http://localhost:3000/api/v1/emails")--http = Net::HTTP.new(url.host, url.port)-request = Net::HTTP::Post.new(url)-request["Accept"] = 'application/json'-request["Content-Type"] = 'application/json'-request["Authorization"] = 'Bearer us_ad9a79256e366399c747cbf0b38eca3c472e8a2e'-request.body = JSON.dump({- "to" => "koushikmohan1996@gmail.com",- "from" => "hello@test.splitpro.app",- "subject" => "Test mail",- "html" => "<p>Hello this is a test mail</p>"-})--response = http.request(request)-puts response.read_body`;--const phpCode = `$url = "http://localhost:3000/api/v1/emails";--$payload = json_encode(array(- "to" => "koushikmohan1996@gmail.com",- "from" => "hello@test.splitpro.app",- "subject" => "Test mail",- "html" => "<p>Hello this is a test mail</p>"-));--$headers = array(- "Accept: application/json",- "Content-Type: application/json",- "Authorization: Bearer us_ad9a79256e366399c747cbf0b38eca3c472e8a2e"-);--$ch = curl_init($url);-curl_setopt($ch, CURLOPT_POST, true);-curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);-curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);-curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);--$response = curl_exec($ch);-if (curl_errno($ch)) {- echo 'Error:' . curl_error($ch);-} else {- echo $response;-}`;

152-159: Rebrand copy and links from Unsend to UseSend.

Update subject/body and link to reflect the new brand.

Apply this diff:

- subject: "Unsend test email",- body: "hello,\\n\\nUnsend is the best open source sending platform",+ subject: "UseSend test email",+ body: "hello,\\n\\nUseSend is the best open source sending platform",
bodyHtml:
- "<p>hello,</p><p>Unsend is the best open source sending platform<p><p>check out <a href='https://unsend.dev'>unsend.dev</a>",+ "<p>hello,</p><p>UseSend is the best open source sending platform<p><p>check out <a href='https://usesend.dev'>usesend.dev</a>",
apps/web/src/app/(dashboard)/suppressions/suppression-list.tsx (2)

168-176: Fix optional chaining to avoid runtime error when data is undefined.

data?.suppressions.map will throw because .map is accessed on possibly undefined. Use chained optional or a fallback array.

Apply this diff:

- ) : suppressionsQuery.data?.suppressions.length === 0 ? (+ ) : ((suppressionsQuery.data?.suppressions?.length ?? 0) === 0) ? (
<TableRow className="h-32">
<TableCell colSpan={4} className="text-center py-4">
No suppressed emails found
</TableCell>
</TableRow>
) : (
- suppressionsQuery.data?.suppressions.map((suppression) => (+ suppressionsQuery.data?.suppressions?.map((suppression) => (
<TableRow key={suppression.id}>

Also applies to: 175-211


83-99: Escape CSV fields and use ISO dates.

Current CSV breaks when values contain commas/quotes. Quote and escape fields; format date as ISO.

Apply this diff:

- const csv = [- "Email,Reason,Created At",- ...resp.data.map(- (suppression) =>- `${suppression.email},${suppression.reason},${suppression.createdAt}`- ),- ].join("\n");+ const esc = (v: unknown) =>+ `"${String(v ?? "").replace(/"/g, '""')}"`;+ const csv = [+ "Email,Reason,Created At",+ ...resp.data.map((s) =>+ [esc(s.email), esc(s.reason), esc(new Date(s.createdAt).toISOString())].join(",")+ ),+ ].join("\n");
apps/web/src/app/(dashboard)/emails/email-list.tsx (1)

20-21: date-fns doesn’t export formatDate; use format.

This currently won’t compile. Replace import and usages.

-import { formatDate, formatDistanceToNow } from "date-fns";+import { format, formatDistanceToNow } from "date-fns";
@@
- {formatDate(+ {format(
email.scheduledAt,
- "MMM dd'th', hh:mm a",+ "MMM do, hh:mm a",
)}
@@
- ? formatDate(+ ? format(
email.scheduledAt ?? email.createdAt,
"MMM do, hh:mm a",
)

Also applies to: 231-236, 248-252

apps/web/src/app/(dashboard)/contacts/[contactBookId]/edit-contact.tsx (1)

140-151: Fix Tailwind class typo breaking layout.

fle prevents flex layout; use flex.

- <FormItem className="fle flex-row gap-2">+ <FormItem className="flex flex-row gap-2">
apps/web/src/components/AppSideBar.tsx (2)

127-133: Update brand text to UseSend

PR goal is rebrand; sidebar header still shows “Unsend”.

- <span className="text-lg font-semibold text-foreground">- Unsend- </span>+ <span className="text-lg font-semibold text-foreground">+ UseSend+ </span>

236-241: Update docs link and rebrand all unsend.dev references

  • Change <Link href="https://docs.unsend.dev"…> to https://docs.usesend.dev in apps/web/src/components/AppSideBar.tsx:236–241.
  • Replace every occurrence of unsend.dev with usesend.dev and update branding from Unsend to UseSend across docs (apps/docs/), Docker files (docker/README.md, apps/smtp-server/docker-compose.yml), SDK (packages/sdk/), email-editor, email templates, and code comments. Use ripgrep to locate all leftovers:
rg -n -S 'unsend\.dev' --glob '!**/node_modules/**' rg -n -S '\bUnsend\b' --glob '!**/node_modules/**'
apps/web/src/app/layout.tsx (1)

15-19: Update app metadata title to UseSend

Branding string still shows “Unsend”.

 export const metadata: Metadata = {
- title: "Unsend",+ title: "UseSend",
description: "Open source sending infrastructure for developers",
icons: [{ rel: "icon", url: "/favicon.ico" }],
};
apps/web/src/app/login/login-page.tsx (1)

130-130: Update branding in UI copy and alt text.

Rebrand references to “UseSend”.

- alt="Unsend"+ alt="UseSend"
- {isSignup ? "Create new account" : "Sign into Unsend"}+ {isSignup ? "Create new account" : "Sign into UseSend"}
- {isSignup ? "Already have an account?" : "New to Unsend?"}+ {isSignup ? "Already have an account?" : "New to UseSend?"}

Also applies to: 137-141

apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx (1)

282-284: Update brand reference in copy.

Change “Unsend” → “UseSend”.

- <p className=" text-muted-foreground text-sm">- Unsend adds a tracking pixel to every email you send. This allows you- to see how many people open your emails. This will affect the delivery+ <p className=" text-muted-foreground text-sm">+ UseSend adds a tracking pixel to every email you send. This allows you+ to see how many people open your emails. This may affect the delivery
rate of your emails.
</p>
apps/web/src/app/(dashboard)/settings/team/edit-team-member.tsx (1)

36-38: Bind schema to Role enum; restrict to allowed roles via refine to avoid drift.

Hardcoding strings can desync with server types. Use z.nativeEnum(Role) and refine to the allowed set.

Apply:

-const teamUserSchema = z.object({- role: z.enum(["MEMBER", "ADMIN"]),-});+const teamUserSchema = z.object({+ role: z.nativeEnum(Role).refine((r) => r === "MEMBER" || r === "ADMIN", {+ message: "Role must be MEMBER or ADMIN",+ }),+});
apps/web/src/app/(dashboard)/settings/team/team-members-list.tsx (1)

75-77: Potential crash when member.user is undefined.

You guard email with optional chaining but not createdAt. This can throw at runtime.

Apply:

- {formatDistanceToNow(new Date(member.user.createdAt), {- addSuffix: true,- })}+ {member.user?.createdAt+ ? formatDistanceToNow(new Date(member.user.createdAt), { addSuffix: true })+ : "—"}
apps/web/src/app/(dashboard)/contacts/[contactBookId]/contact-list.tsx (1)

108-109: Fix Tailwind class typo: border-broder → border-border.

Currently the border style won’t apply.

-<div className="flex flex-col rounded-xl border border-broder shadow">+<div className="flex flex-col rounded-xl border border-border shadow">
apps/web/src/app/(dashboard)/payments/page.tsx (1)

31-48: Fix false positive “upgraded” state when data is undefined or still loading.

teams?.[0]?.plan !== "FREE" evaluates true for undefined, showing “upgraded” prematurely. Also stop polling after upgrade.

Apply:

 function VerifySuccess() {
- const { data: teams, isLoading } = api.team.getTeams.useQuery(undefined, {- refetchInterval: 3000,- });+ const { data: teams, isLoading } = api.team.getTeams.useQuery(undefined, {+ refetchInterval: (data) =>+ data?.[0]?.plan && data[0].plan !== "FREE" ? false : 3000,+ });- if (teams?.[0]?.plan !== "FREE") {+ const plan = teams?.[0]?.plan;+ if (!isLoading && plan && plan !== "FREE") {
return (
<div>
<div className="flex gap-2 items-center">
- <CheckCircle2 className="h-4 w-4 text-green flex-shrink-0" />+ <CheckCircle2 className="h-4 w-4 text-green flex-shrink-0" />
<p>Your account has been upgraded to the paid plan.</p>
</div>
<Link href="/settings/billing" className="mt-8">
<Button className="mt-8">Go to billing</Button>
</Link>
</div>
);
}
♻️ Duplicate comments (3)
apps/web/src/app/(dashboard)/dev-settings/smtp/page.tsx (1)

8-10: Deep import note (same as campaigns/duplicate-campaign.tsx).

Same recommendation: prefer package entrypoints over @usesend/ui/src/* deep paths if available.

apps/web/src/app/(dashboard)/suppressions/remove-suppression.tsx (1)

10-12: Deep import note (duplicate).

Prefer @usesend/ui entrypoints over @usesend/ui/src/* deep imports if the package exports them.

apps/web/src/components/settings/AddSesSettings.tsx (1)

12-12: Deep import note.

Same as other files: consider switching to stable package entrypoints.

Also applies to: 16-20

Comment thread.github/workflows/publish.yml Outdated
Comment thread.github/workflows/publish.yml Outdated
Comment on lines 100 to 103
const [json, setJson] = useState<Record<string, any> | undefined>(
campaign.content ? JSON.parse(campaign.content) : undefined
campaign.content ? JSON.parse(campaign.content) : undefined,
);
const [isSaving, setIsSaving] = useState(false);

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.

🛠️ Refactor suggestion

Defensive JSON parse for campaign.content and avoid any.

Guard against invalid JSON and prefer unknown.

- const [json, setJson] = useState<Record<string, any> | undefined>(- campaign.content ? JSON.parse(campaign.content) : undefined,- );+ const [json, setJson] = useState<Record<string, unknown> | undefined>(() => {+ try {+ return campaign.content ? JSON.parse(campaign.content) : undefined;+ } catch {+ toast.error("Invalid campaign content. Resetting.");+ return undefined;+ }+ });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/src/app/(dashboard)/campaigns/[campaignId]/edit/page.tsx around
lines 100 to 103, the JSON parse is not defensive and uses Record<string, any>;
change to use Record<string, unknown> and wrap JSON.parse in a try/catch (or a
safe parser helper) so invalid JSON falls back to undefined (or an empty object)
instead of throwing; initialize state with the safely-parsed value and update
setJson to accept Record<string, unknown> | undefined, ensuring you
narrow/validate the unknown before using it elsewhere.

Comment on lines +3 to +10
import { Button } from "@usesend/ui/src/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from "@unsend/ui/src/select";
import Spinner from "@unsend/ui/src/spinner";
} from "@usesend/ui/src/select";
import Spinner from "@usesend/ui/src/spinner";

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.

💡 Verification agent

🧩 Analysis chain

Avoid deep /src imports; use public @usesend/ui entrypoints.

Align with package boundaries to prevent breakage when UI internals change.

-import { Button } from "@usesend/ui/src/button";+import { Button } from "@usesend/ui/button";
@@
-} from "@usesend/ui/src/select";+} from "@usesend/ui/select";
@@
-import Spinner from "@usesend/ui/src/spinner";+import Spinner from "@usesend/ui/spinner";
@@
-} from "@usesend/ui/src/table";+} from "@usesend/ui/table";
@@
-import { Input } from "@usesend/ui/src/input";+import { Input } from "@usesend/ui/input";
@@
-} from "@usesend/ui/src/tooltip";+} from "@usesend/ui/tooltip";

Also applies to: 18-18, 26-26, 33-33


🏁 Script executed:

#!/bin/bash# ensure no lingering deep imports in this app
rg -nP $'^import.+@usesend/ui/src/' apps/web -S

Length of output: 18009


Replace all deep @usesend/ui/src imports with public entrypoints
Deep imports are used throughout apps/web (40+ occurrences); update each to the corresponding public path (e.g. @usesend/ui/button, @usesend/ui/select, @usesend/ui/spinner, etc.) to respect package boundaries and avoid future breakage. Add an ESLint rule (e.g. import/no-internal-modules) to prevent regressions.

🤖 Prompt for AI Agents
In apps/web/src/app/(dashboard)/contacts/[contactBookId]/contact-list.tsx around
lines 3 to 10, the file uses deep imports from @usesend/ui/src (e.g.
@usesend/ui/src/button, /select, /spinner); replace each deep import with the
published public entrypoints (e.g. @usesend/ui/button, @usesend/ui/select,
@usesend/ui/spinner) so package boundaries are respected and imports remain
stable, and update any other occurrences in apps/web accordingly; finally add or
enable an ESLint rule such as import/no-internal-modules (configured to disallow
@usesend/ui/**/src/*) to prevent future deep-import regressions.

import { useIsMobile } from "@unsend/ui/src/hooks/use-mobile";
import { SidebarInset, SidebarTrigger } from "@usesend/ui/src/sidebar";
import { SidebarProvider } from "@usesend/ui/src/sidebar";
import { useIsMobile } from "@usesend/ui/src/hooks/use-mobile";

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.

💡 Verification agent

🧩 Analysis chain

Use public entrypoint for the hook.

Avoid deep “/src” import for stability.

-import { useIsMobile } from "@usesend/ui/src/hooks/use-mobile";+import { useIsMobile } from "@usesend/ui/hooks/use-mobile";

Verify the hook’s subpath export exists:


🏁 Script executed:

#!/bin/bashset -euo pipefail
rg -nP -C1 'useIsMobile' packages/ui ||true
jq -r '.exports' packages/ui/package.json 2>/dev/null | rg -n '\./hooks/use-mobile'||true

Length of output: 739


Add public export for hooks/use-mobile before switching import
The UI package doesn’t expose hooks/use-mobile via its root entrypoint. In packages/ui/package.json under "exports", add:

 "exports": {
"…": "…",
+ "./hooks/use-mobile": "./src/hooks/use-mobile"
}

Then you can safely update:

-import { useIsMobile } from "@usesend/ui/src/hooks/use-mobile";+import { useIsMobile } from "@usesend/ui/hooks/use-mobile";
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import{useIsMobile}from"@usesend/ui/src/hooks/use-mobile";
// apps/web/src/app/(dashboard)/dashboard-layout.tsx
import{useIsMobile}from"@usesend/ui/hooks/use-mobile";
🤖 Prompt for AI Agents
In apps/web/src/app/(dashboard)/dasboard-layout.tsx around line 6, the file
imports the hook directly from an internal path
("@usesend/ui/src/hooks/use-mobile") which is not exported by the UI package;
update the UI package exports in packages/ui/package.json to add a public export
entry for the hooks/use-mobile module (pointing to the hook's source file), then
change the import in dasboard-layout.tsx to use the package public path (e.g.
"@usesend/ui/hooks/use-mobile") so the hook is consumed via the package
entrypoint.

Comment on lines +9 to +10
`${path.join(require.resolve("@usesend/ui"), "..")}/**/*.{ts,tsx}`,
`${path.join(require.resolve("@usesend/email-editor"), "..")}/**/*.{ts,tsx}`,

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.

💡 Verification agent

🧩 Analysis chain

Resolve package roots via package.json; avoid require.resolve + ".." (fragile) and ESM require pitfalls.

require.resolve(pkg) returns the entry file (often dist/index.js). Joining “..” can land you in dist/, causing Tailwind to miss TS/TSX sources. Also, in ESM require is undefined. Prefer resolving the package.json and taking its dirname, and polyfilling require via createRequire when ESM.

Apply within these lines:

- `${path.join(require.resolve("@usesend/ui"), "..")}/**/*.{ts,tsx}`,- `${path.join(require.resolve("@usesend/email-editor"), "..")}/**/*.{ts,tsx}`,+ `${uiDir}/**/*.{ts,tsx}`,+ `${emailEditorDir}/**/*.{ts,tsx}`,

Add near the top (outside the changed hunk) to be ESM-safe and robust:

import{createRequire}from"module";constrequire=createRequire(import.meta.url);constuiDir=path.dirname(require.resolve("@usesend/ui/package.json"));constemailEditorDir=path.dirname(require.resolve("@usesend/email-editor/package.json"));

Run to verify module type and package entry structure:


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"apps/web package type:"
jq -r '.type // "(not set)"' apps/web/package.json ||trueecho"root package type:"
jq -r '.type // "(not set)"' package.json ||trueecho"@usesend/ui package.json locations and entry fields:"
rg -nP '"name"\s*:\s*"@usesend/ui"' -g '**/package.json' -l |whileread -r p;doecho"File: $p"
jq -r '{name, main, module, exports}'"$p"doneecho"@usesend/email-editor package.json locations and entry fields:"
rg -nP '"name"\s*:\s*"@usesend/email-editor"' -g '**/package.json' -l |whileread -r p;doecho"File: $p"
jq -r '{name, main, module, exports}'"$p"done

Length of output: 737


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"apps/web package type:" jq -r '.type // "(not set)"' apps/web/package.json
echo"root package type:"
jq -r '.type // "(not set)"' package.json ||trueecho -e "\npackages/ui/package.json (first 50 lines):"
head -n50 packages/ui/package.json
echo -e "\npackages/email-editor/package.json (first 50 lines):"
head -n50 packages/email-editor/package.json

Length of output: 3539


Use createRequire and resolve package.json to derive package roots
In ESM configs, require is undefined and relying on require.resolve(pkg) + ".." is fragile if a package’s main field changes. Instead, polyfill require and resolve each package’s root via its package.json:

import{createRequire}from"module";constrequire=createRequire(import.meta.url);constuiDir=path.dirname(require.resolve("@usesend/ui/package.json"));constemailEditorDir=path.dirname(require.resolve("@usesend/email-editor/package.json"));

Replace lines 9–10 in apps/web/tailwind.config.ts:

- `${path.join(require.resolve("@usesend/ui"), "..")}/**/*.{ts,tsx}`,- `${path.join(require.resolve("@usesend/email-editor"), "..")}/**/*.{ts,tsx}`,+ `${uiDir}/**/*.{ts,tsx}`,+ `${emailEditorDir}/**/*.{ts,tsx}`,
🤖 Prompt for AI Agents
In apps/web/tailwind.config.ts around lines 9 to 10, the config uses
require.resolve(...) and path.join(...,"..") which breaks in ESM and is fragile;
replace this by importing createRequire from "module", creating a require with
import.meta.url, then resolve each package's package.json and use path.dirname
to get the package root (e.g., uiDir and emailEditorDir), and finally reference
`${uiDir}/**/*.{ts,tsx}` and `${emailEditorDir}/**/*.{ts,tsx}` in place of the
current lines so resolution works in ESM and is robust to changes in package
main fields.

import React, { useRef } from "react";
import { TextMenu } from "./menus/TextMenu";
import { cn } from "@unsend/ui/lib/utils";
import { cn } from "@usesend/ui/lib/utils";

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.

💡 Verification agent

🧩 Analysis chain

Complete rebrand in editor: update brand strings, sample code, classes; and group imports.

The content, link, sample code, and CSS classes still reference “Unsend”. Also, place external imports before local ones per guidelines.

- import { TextMenu } from "./menus/TextMenu";-import { cn } from "@usesend/ui/lib/utils";+import { cn } from "@usesend/ui/lib/utils";+import { TextMenu } from "./menus/TextMenu";
@@
-<h3>Unsend is the best open source resend alternative.</h3>+<h3>UseSend is the best open source Resend alternative.</h3>
@@
-you can change <span style="color: #dc2626;"> color</span> of text. Add <a href="https://unsend.dev" target="_blank">link</a> to text+you can change <span style="color: #dc2626;"> color</span> of text. Add <a href="https://usesend.dev" target="_blank">link</a> to text
@@
-const unsend = new Unsend("us_12345");+const usesend = new UseSend("us_12345");
@@
-// const unsend = new Unsend("us_12345", "https://my-unsend-instance.com");+// const usesend = new UseSend("us_12345", "https://my-usesend-instance.com");
@@
- class: cn("unsend-prose w-full"),+ class: cn("usesend-prose w-full"),
@@
- <div- className="bg-white rounded-md text-black p-8 unsend-editor light"+ <div+ className="bg-white rounded-md text-black p-8 usesend-editor light"

Optional follow-up: if classnames are used in CSS, ensure corresponding styles are renamed accordingly.

To catch any remaining references across the repo, run:


🏁 Script executed:

#!/bin/bash
rg -nP -C1 '(?i)\bunsend\b|unsend\.dev|unsend-editor|unsend-prose|new\s+Unsend\('

Length of output: 64853


Complete rebrand in email-editor package: update imports, brand strings, sample code, CSS, and extension attributes

  • In packages/email-editor/src/editor.tsx: reorder imports (cn before local), update <h3>…Unsend… to “UseSend”, change sample link to https://usesend.dev, rename const unsend = new Unsend to const usesend = new UseSend, and update class names to usesend-prose/usesend-editor.
  • In packages/email-editor/src/styles/index.css: rename all .unsend-editor.usesend-editor and .unsend-prose.usesend-prose.
  • In packages/email-editor/package.json: update the description field to reference “UseSend” instead of “unsend”.
  • In packages/email-editor/src/extensions/*: rename all data-unsend-component attributes to data-usesend-component.
🤖 Prompt for AI Agents
In packages/email-editor/src/editor.tsx around line 13, the import order,
branding identifiers, sample link, and CSS/class names were not fully updated
for the UseSend rebrand; move the third-party import (cn) before the local
imports, rename any class names and variables: change "<h3>…Unsend…" to
"UseSend", update sample link to "https://usesend.dev", rename "const unsend =
new Unsend" to "const usesend = new UseSend", and replace class names
"unsend-prose"/"unsend-editor" with "usesend-prose"/"usesend-editor"; also
ensure corresponding attributes and names in related files (styles and
extensions) match these new identifiers.

Comment threadREADME.md
Comment on lines +9 to 17
<a href="https://usesend.com"><strong>Learn more »</strong></a>
<br />
<br />
<a href="https://discord.gg/BU8n8pJv8S">Discord</a>
.
<a href="https://unsend.dev">Website</a>
<a href="https://usesend.com">Website</a>
·
<a href="https://github.com/unsend-dev/unsend/issues">Issues</a>
<a href="https://github.com/usesend/usesend/issues">Issues</a>
</p>

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.

💡 Verification agent

🧩 Analysis chain

Verify new links resolve (Website/Issues/Discord)

Quick health-check the new URLs to avoid broken badges/anchors post-rebrand.


🏁 Script executed:

#!/bin/bashset -euo pipefail
forurlin \
"https://usesend.com" \
"https://github.com/usesend/usesend/issues" \
"https://discord.gg/BU8n8pJv8S"doecho"Checking: $url"
curl -I -sS --max-time 10 "$url"| sed -n '1p'done

Length of output: 873


Fix broken GitHub Issues link and adjust Discord link in README.md

  • The GitHub issues URL (https://github.com/usesend/usesend/issues) returns HTTP 404; please update it to the correct repository’s issues page.
  • The Discord link (https://discord.gg/BU8n8pJv8S) returns a 301 redirect; replace it with the canonical invite URL (e.g., https://discord.com/invite/BU8n8pJv8S) to avoid redirects.
🤖 Prompt for AI Agents
In README.md around lines 9 to 17, the GitHub issues link is broken and the
Discord invite uses a redirect; update the GitHub issues URL to the correct
repository issues page (verify the org/repo name in the project and replace
https://github.com/usesend/usesend/issues with the correct
https://github.com/<org>/<repo>/issues), and replace the redirecting Discord URL
with the canonical invite form https://discord.com/invite/BU8n8pJv8S to avoid
301 redirects.

Comment threadREADME.md
Comment on lines +28 to 29
<img src="https://github.com/usesend/usesend/assets/24666922/68c41a6f-8fd1-4a3e-8d9b-987dda105c22" style="width: 100%;" />
</div>

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.

🛠️ Refactor suggestion

Add alt text for the hero image (accessibility, MD045)

The image is missing alt text; add a concise description.

- <img src="https://github.com/usesend/usesend/assets/24666922/68c41a6f-8fd1-4a3e-8d9b-987dda105c22" style="width: 100%;" />+ <img src="https://github.com/usesend/usesend/assets/24666922/68c41a6f-8fd1-4a3e-8d9b-987dda105c22" alt="useSend dashboard preview" style="width: 100%;" />
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<imgsrc="https://github.com/usesend/usesend/assets/24666922/68c41a6f-8fd1-4a3e-8d9b-987dda105c22"style="width: 100%;" />
</div>
<imgsrc="https://github.com/usesend/usesend/assets/24666922/68c41a6f-8fd1-4a3e-8d9b-987dda105c22"alt="useSend dashboard preview"style="width: 100%;" />
</div>
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

28-28: Images should have alternate text (alt text)

(MD045, no-alt-text)

🤖 Prompt for AI Agents
In README.md around lines 28-29, the hero image tag lacks alt text which fails
MD045 accessibility checks; add a concise descriptive alt attribute to the img
element (e.g., alt="Usesend product hero illustration" or similar brief
description relevant to the image) ensuring it describes the image purpose for
screen readers and preserves the existing src and style attributes.

Comment threadREADME.md
Comment on lines +75 to 80
Check out the editor code for [here](https://github.com/usesend/usesend/tree/main/packages/email-editor). Editor is possible only because of the amazing tools and libraries.

- [jsx-email](https://jsx.email/) - converts editor content to html
- [maily.to](https://maily.to/) - unsend email editor is greatly inspired from maily.to
- [maily.to](https://maily.to/) - useSend email editor is greatly inspired from maily.to
- [tiptap](https://tiptap.dev/) - editor core

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.

🛠️ Refactor suggestion

Editor section: grammar and tone (“amazing”, “inspired from”)

Make the sentence direct; replace “amazing” and “inspired from”.

-Check out the editor code for [here](https://github.com/usesend/usesend/tree/main/packages/email-editor). Editor is possible only because of the amazing tools and libraries.+Check out the editor code [here](https://github.com/usesend/usesend/tree/main/packages/email-editor). The editor is possible thanks to the following tools and libraries:
@@
-- [maily.to](https://maily.to/) - useSend email editor is greatly inspired from maily.to+- [maily.to](https://maily.to/) - the useSend editor is greatly inspired by maily.to
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Check out the editor code for [here](https://github.com/usesend/usesend/tree/main/packages/email-editor). Editor is possible only because of the amazing tools and libraries.
-[jsx-email](https://jsx.email/) - converts editor content to html
-[maily.to](https://maily.to/) - unsend email editor is greatly inspired from maily.to
-[maily.to](https://maily.to/) - useSend email editor is greatly inspired from maily.to
-[tiptap](https://tiptap.dev/) - editor core
Check out the editor code [here](https://github.com/usesend/usesend/tree/main/packages/email-editor). The editor is possible thanks to the following tools and libraries:
-[jsx-email](https://jsx.email/) - converts editor content to html
-[maily.to](https://maily.to/) - the useSend editor is greatly inspired by maily.to
-[tiptap](https://tiptap.dev/) - editor core
🧰 Tools
🪛 LanguageTool

[style] ~75-~75: Consider using a more formal and expressive alternative to ‘amazing’.
Context: ... Editor is possible only because of the amazing tools and libraries. - [jsx-email](htt...

(AWESOME)


[grammar] ~78-~78: There might be a mistake here.
Context: ...editor is greatly inspired from maily.to - tiptap - editor co...

(QB_NEW_EN)

🤖 Prompt for AI Agents
In README.md around lines 75 to 80, the Editor section uses informal/awkward
wording ("amazing" and "inspired from"); reword the paragraph to be direct and
neutral: remove "amazing" and change "inspired from" to "inspired by" (or state
"based on"), e.g. describe the editor dependencies as "The editor uses the
following libraries" and list each library with a short neutral description.

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 0

♻️ Duplicate comments (1)
.github/workflows/publish.yml (1)

120-139: Loop target fixed from “unsend” → “usesend” — resolved.

This addresses the prior manifest-loop issue.

🧹 Nitpick comments (2)
.github/workflows/publish.yml (2)

68-73: Prevent accidental “:undefined” tags on manual runs.

If HEAD isn’t on a tag (e.g., workflow_dispatch), APP_VERSION becomes “undefined”; the current build will tag/push that. Build only adds APP_VERSION tags when it’s set.

Apply:

- docker build \+ TAG_ARGS=( \
-f "$DOCKER_FILE" \
--progress=plain \
- -t "usesend/$APP-$BUILD_PLATFORM:latest" \- -t "usesend/$APP-$BUILD_PLATFORM:$GIT_SHA" \- -t "usesend/$APP-$BUILD_PLATFORM:$APP_VERSION" \- -t "ghcr.io/usesend/$APP-$BUILD_PLATFORM:latest" \- -t "ghcr.io/usesend/$APP-$BUILD_PLATFORM:$GIT_SHA" \- -t "ghcr.io/usesend/$APP-$BUILD_PLATFORM:$APP_VERSION" \- "$CONTEXT"+ -t "usesend/$APP-$BUILD_PLATFORM:latest" \+ -t "usesend/$APP-$BUILD_PLATFORM:$GIT_SHA" \+ -t "ghcr.io/usesend/$APP-$BUILD_PLATFORM:latest" \+ -t "ghcr.io/usesend/$APP-$BUILD_PLATFORM:$GIT_SHA" \+ )+ if [[ "$APP_VERSION" != "undefined" && -n "$APP_VERSION" ]]; then+ TAG_ARGS+=( \+ -t "usesend/$APP-$BUILD_PLATFORM:$APP_VERSION" \+ -t "ghcr.io/usesend/$APP-$BUILD_PLATFORM:$APP_VERSION" \+ )+ fi+ docker build "${TAG_ARGS[@]}" "$CONTEXT"

120-139: Make manifest steps idempotent and avoid “undefined” version.

  • Remove existing manifests before recreate to survive re-runs.
  • Skip $APP_VERSION manifests if not on a tag.
  • DRY the app list to avoid drift between DockerHub and GHCR loops.

Apply:

- for APP_NAME in usesend smtp-proxy; do+ APP_NAMES="usesend smtp-proxy"+ for APP_NAME in $APP_NAMES; do
docker manifest create \
usesend/$APP_NAME:latest \
--amend usesend/$APP_NAME-amd64:latest \
--amend usesend/$APP_NAME-arm64:latest
+ docker manifest rm usesend/$APP_NAME:latest || true
docker manifest create \
usesend/$APP_NAME:$GIT_SHA \
--amend usesend/$APP_NAME-amd64:$GIT_SHA \
--amend usesend/$APP_NAME-arm64:$GIT_SHA
+ docker manifest rm usesend/$APP_NAME:$GIT_SHA || true-- docker manifest create \- usesend/$APP_NAME:$APP_VERSION \- --amend usesend/$APP_NAME-amd64:$APP_VERSION \- --amend usesend/$APP_NAME-arm64:$APP_VERSION+ if [[ "$APP_VERSION" != "undefined" && -n "$APP_VERSION" ]]; then+ docker manifest create \+ usesend/$APP_NAME:$APP_VERSION \+ --amend usesend/$APP_NAME-amd64:$APP_VERSION \+ --amend usesend/$APP_NAME-arm64:$APP_VERSION+ docker manifest rm usesend/$APP_NAME:$APP_VERSION || true+ fi-- docker manifest push usesend/$APP_NAME:latest- docker manifest push usesend/$APP_NAME:$GIT_SHA- docker manifest push usesend/$APP_NAME:$APP_VERSION+ docker manifest push usesend/$APP_NAME:latest+ docker manifest push usesend/$APP_NAME:$GIT_SHA+ if [[ "$APP_VERSION" != "undefined" && -n "$APP_VERSION" ]]; then+ docker manifest push usesend/$APP_NAME:$APP_VERSION+ fi
done
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 35b5d26 and ca6e4c9.

📒 Files selected for processing (4)
  • .github/workflows/publish.yml (3 hunks)
  • apps/smtp-server/docker-compose.yml (1 hunks)
  • apps/smtp-server/src/server.ts (5 hunks)
  • packages/sdk/LICENSE (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/sdk/LICENSE
🧰 Additional context used
📓 Path-based instructions (6)
{apps,packages}/**/*.{js,jsx,ts,tsx,css,scss,md,mdx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier with the Tailwind plugin for code formatting

Files:

  • apps/smtp-server/src/server.ts
{apps,packages}/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{js,jsx,ts,tsx}: Group imports by source (internal/external) and alphabetize them
Use camelCase for variables and functions, PascalCase for components and classes
Use try/catch with specific error types for error handling

Files:

  • apps/smtp-server/src/server.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{ts,tsx}: Use strong typing in TypeScript, avoid any, and use Zod for validation
Follow Vercel style guides with strict TypeScript

Files:

  • apps/smtp-server/src/server.ts
apps/smtp-server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Hono for public API endpoints

Files:

  • apps/smtp-server/src/server.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Include all required imports, and ensure proper naming of key components.

Files:

  • apps/smtp-server/src/server.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation and semicolons (enforced by Prettier) in TypeScript files

Files:

  • apps/smtp-server/src/server.ts
🧠 Learnings (1)
📚 Learning: 2025-08-09T10:37:58.146Z
Learnt from: CR
PR: unsend-dev/unsend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-09T10:37:58.146Z
Learning: Applies to apps/smtp-server/**/*.{ts,tsx} : Use Hono for public API endpoints

Applied to files:

  • apps/smtp-server/src/server.ts
🔇 Additional comments (13)
.github/workflows/publish.yml (3)

77-77: Push will include all tags — relies on the build fix above.

Once APP_VERSION tagging is conditional, this will no longer push an “undefined” tag. No change needed here after the build tweak.


146-165: GHCR org usesend confirmed—workflow has API access. Optional: apply APP_NAMES DRY, idempotency (docker manifest rm), and APP_VERSION guard as per suggested diff.


27-33: Matrix app rename matches rebrand — LGTM
Verified Dockerfile paths at docker/Dockerfile and apps/smtp-server/Dockerfile exist.

apps/smtp-server/docker-compose.yml (3)

6-6: LGTM: Docker image updated for rebranding.

The container image reference has been correctly updated from unsend/smtp-proxy:latest to usesend/smtp-proxy:latest to align with the project rebranding.


9-9: LGTM: SMTP auth username updated consistently.

The SMTP authentication username has been updated from "unsend" to "usesend" which matches the corresponding change in apps/smtp-server/src/server.ts Line 9.


10-10: LGTM: Base URL updated to new domain.

The base URL has been correctly updated from "https://app.unsend.dev" to "https://app.usesend.com" which aligns with the rebranding and matches the change in apps/smtp-server/src/server.ts Line 11.

apps/smtp-server/src/server.ts (7)

9-11: LGTM: Environment defaults updated for rebranding.

The default values for AUTH_USERNAME and UNSEND_BASE_URL have been correctly updated to reflect the UseSend rebranding. These changes are consistent with the corresponding updates in the Docker Compose configuration.


15-15: LGTM: Function renamed for consistency.

The function has been appropriately renamed from sendEmailToUnsend to sendEmailToUseSend to align with the rebranding initiative.


19-19: LGTM: Logging messages updated for rebranding.

All console log and error messages have been consistently updated to reference "useSend" instead of "Unsend", maintaining clear and accurate logging throughout the application.

Also applies to: 35-37, 40-40, 45-45


46-54: LGTM: Improved error handling with consistent messaging.

The error handling has been enhanced with consistent "Failed to send email:" prefix for both Error instances and unexpected errors. This provides better error propagation and clearer error messages.


64-64: LGTM: Code formatting improvement.

The trailing comma in the callback type signature improves code consistency and follows modern TypeScript formatting practices.


91-91: LGTM: Function call updated consistently.

The function call has been correctly updated to use the renamed sendEmailToUseSend function, maintaining the same functionality while aligning with the rebranding.


120-121: LGTM: Formatting consistency maintained.

The trailing comma in the console.log string maintains consistent formatting throughout the codebase.

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (14)
apps/web/src/lib/constants/example-codes.ts (4)

26-32: Escape interpolated values to keep example code valid for quotes/newlines

Directly embedding from/subject/body/bodyHtml can break the snippet (e.g., quotes, newlines) and risk mis-highlighting. Emit JSON-escaped literals instead.

Apply:

-usesend.emails.send({- to: "${to}",- from: "${from}",- subject: "${subject}",- html: "${bodyHtml}",- text: "${body}",-});+usesend.emails.send({+ to: ${JSON.stringify(to)},+ from: ${JSON.stringify(from)},+ subject: ${JSON.stringify(subject)},+ html: ${JSON.stringify(bodyHtml)},+ text: ${JSON.stringify(body)},+});

36-56: Python: escape values and prefer requests.post for clarity

Ensure payload strings are safely represented and use the typed helper.

-url = "https://app.usesend.com/api/v1/emails"--payload = {- "to": "${to}",- "from": "${from}",- "subject": "${subject}",- "text": "${body}",- "html": "${bodyHtml}",-}+url = "https://app.usesend.com/api/v1/emails"++payload = {+ "to": ${JSON.stringify(to)},+ "from": ${JSON.stringify(from)},+ "subject": ${JSON.stringify(subject)},+ "text": ${JSON.stringify(body)},+ "html": ${JSON.stringify(bodyHtml)},+}
@@
-response = requests.request("POST", url, json=payload, headers=headers)+response = requests.post(url, json=payload, headers=headers)

60-79: PHP: avoid giant JSON string; build array and json_encode it

This prevents quoting/escaping bugs for dynamic content.

-<?php--$curl = curl_init();--curl_setopt_array($curl, [- CURLOPT_URL => "https://app.usesend.com/api/v1/emails",- CURLOPT_RETURNTRANSFER => true,- CURLOPT_ENCODING => "",- CURLOPT_MAXREDIRS => 10,- CURLOPT_TIMEOUT => 30,- CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,- CURLOPT_CUSTOMREQUEST => "POST",- CURLOPT_POSTFIELDS => "{\n \"to\": \"${to}\",\n \"from\": \"${from}\",\n \"subject\": \"${subject}\",\n \"replyTo\": \"${from}\",\n \"text\": \"${body}\",\n \"html\": \"${bodyHtml}\"\n}",- CURLOPT_HTTPHEADER => [- "Authorization: Bearer us_12345",- "Content-Type: application/json"- ],-]);+<?php++$curl = curl_init();++$payload = [+ "to" => ${JSON.stringify(to)},+ "from" => ${JSON.stringify(from)},+ "subject" => ${JSON.stringify(subject)},+ "text" => ${JSON.stringify(body)},+ "html" => ${JSON.stringify(bodyHtml)},+];++curl_setopt_array($curl, [+ CURLOPT_URL => "https://app.usesend.com/api/v1/emails",+ CURLOPT_RETURNTRANSFER => true,+ CURLOPT_ENCODING => "",+ CURLOPT_MAXREDIRS => 10,+ CURLOPT_TIMEOUT => 30,+ CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,+ CURLOPT_CUSTOMREQUEST => "POST",+ CURLOPT_POSTFIELDS => json_encode($payload),+ CURLOPT_HTTPHEADER => [+ "Authorization: Bearer us_12345",+ "Content-Type: application/json"+ ],+]);

100-113: Ruby: escape dynamic values in payload

Avoid raw interpolation to keep JSON valid for quotes/newlines.

-payload = {- "to" => "${to}",- "from" => "${from}",- "subject" => "${subject}",- "text" => "${body}",- "html" => "${bodyHtml}"-}.to_json+payload = {+ "to" => ${JSON.stringify(to)},+ "from" => ${JSON.stringify(from)},+ "subject" => ${JSON.stringify(subject)},+ "text" => ${JSON.stringify(body)},+ "html" => ${JSON.stringify(bodyHtml)}+}.to_json
apps/web/src/app/api/dev/email-preview/route.ts (1)

1-47: Normalize all remaining “Unsend” branding and legacy domains
Found multiple instances of the deprecated “Unsend” alias and URLs—please update to the new “useSend” brand and app.usesend.com domain:

  • packages/sdk/index.ts: remove the deprecated export { UseSend as Unsend } alias (line 2).
  • apps/docs/community-sdk/*.mdx: replace “Unsend” package name and URLs (https://app.unsend.dev, unsend.dev) with “useSend” and https://app.usesend.com.
  • apps/docs/api-reference/introduction.mdx: update “Usend’s API” to “useSend’s API”.
  • apps/web/src/components/AppSideBar.tsx: change the sidebar title “Unsend” to “useSend”.
  • apps/web/src/server/service/ses-hook-parser.ts & ses-settings-service.ts: update header checks and error messages from X-Unsend-Email-ID/“Unsend URL” to X-Usesend-Email-ID/“useSend URL”.
  • apps/web/src/server/aws/ses.ts: remove X-Unsend-Email-ID header and use X-Usesend-Email-ID only.
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx: update copy describing “Unsend adds a tracking pixel…” to “useSend adds a tracking pixel…”.
  • apps/web/src/server/api/routers/domain.ts: update test email subject, text, and HTML content (“Unsend test email”, links to unsend.dev) to “useSend test email” and app.usesend.com.

Run a final grep for /unsend/i to confirm no leftovers.

apps/docs/api-reference/openapi.json (3)

1-11: Add global security to satisfy linters and docs accuracy.

You define the Bearer scheme but don’t apply it. Add a top-level security section so all operations require auth by default.

 "servers": [
{
"url": "https://app.usesend.com/api"
}
],
+ "security": [+ { "Bearer": [] }+ ],

Operations that are public can override with "security": [].


242-253: Path parameter must be required (and not nullable).

/v1/domains/{id}/verify declares id as required: false and nullable: true, which is invalid for a path param.

- "schema": {- "type": "number",- "nullable": true,- "example": 1- },- "required": false,+ "schema": {+ "type": "number",+ "example": 1+ },+ "required": true,

25-33: Fix copy-paste response descriptions.

Multiple responses say “Retrieve the user” for non-user endpoints. Update to accurate, endpoint-specific descriptions.

I can generate a quick diff to normalize these descriptions across the file—want me to proceed?

Also applies to: 469-479, 1000-1014, 1461-1471

apps/docs/api-reference/introduction.mdx (2)

3-3: Fix brand capitalization in frontmatter.

Use “useSend’s” for consistency.

-description: "Fundamental concepts of Usend's API."+description: "Fundamental concepts of useSend's API."

18-18: Fix brand capitalization in body.

Change “Usend’s API” → “useSend’s API”.

-Authentication to Usend's API is performed via the Authorization header with a Bearer token. To authenticate, you need to include the Authorization header with the word Bearer followed by your token in your API requests like so:+Authentication to useSend's API is performed via the Authorization header with a Bearer token. To authenticate, include the Authorization header with the word Bearer followed by your token in your API requests like so:
apps/web/src/server/email-templates/OtpEmail.tsx (1)

39-50: Avoid hardcoding the brand; use the hostName prop in copy.

Prevents future rebrands and enables white-labeling.

- Use the verification code below to sign in to your useSend account:+ Use the verification code below to sign in to your {hostName} account:
apps/docs/get-started/local.mdx (1)

134-139: Update AWS credentials link
The URL https://docs.usesend.com/get-started/create-aws-credentials is unreachable—docs aren’t hosted at that subdomain. Please correct it to the live documentation path for “Create AWS credentials” or publish the docs before merging.

docker/prod/compose.yml (1)

34-45: Align MinIO creds/paths with the rebrand and remove weak defaults.

  • Still uses “unsend” for root user and data dir; update to “usesend”.
  • Don’t ship a hard-coded password in prod.

Apply:

- MINIO_ROOT_USER: unsend- MINIO_ROOT_PASSWORD: password+ MINIO_ROOT_USER: usesend+ MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?err}
@@
- command: -c 'mkdir -p /data/unsend && minio server /data --console-address ":9001" --address ":9002"'+ command: -c 'mkdir -p /data/usesend && minio server /data --console-address ":9001" --address ":9002"'
apps/web/src/server/mailer.ts (1)

111-131: Bug: USESEND_API_KEY not honored; sending path won’t execute.
The condition only checks UNSEND_API_KEY. If only USESEND_API_KEY is set (new default), it throws.

- } else if (env.UNSEND_API_KEY && env.FROM_EMAIL) {+ } else if ((env.USESEND_API_KEY || env.UNSEND_API_KEY) && env.FROM_EMAIL) {
@@
- if (resp.data) {- logger.info("Email sent using usesend");+ if (resp.data) {+ logger.info("Email sent using useSend");
return;
} else {
logger.error(
{ code: resp.error?.code, message: resp.error?.message },
- "Error sending email using usesend, so fallback to resend",+ "Error sending email using useSend, so fallback to resend",
);
}
} else {
throw new Error("USESEND_API_KEY/UNSEND_API_KEY not found");
-}+}
♻️ Duplicate comments (3)
apps/web/src/lib/constants/example-codes.ts (1)

20-26: Variable name mismatch fix looks good

The earlier “usesend vs unsend” error is resolved; call site now matches the instantiated variable.

packages/email-editor/src/editor.tsx (2)

12-16: Order imports: external before local

Move @usesend import above local imports per guidelines.

-import { TextMenu } from "./menus/TextMenu";-import { cn } from "@usesend/ui/lib/utils";+import { cn } from "@usesend/ui/lib/utils";+import { TextMenu } from "./menus/TextMenu";
import { extensions } from "./extensions";

85-86: Rename CSS classnames to usesend-…

Finish the rebrand: unsend-prose/editor → usesend-prose/editor. Ensure corresponding CSS is updated.

- class: cn("unsend-prose w-full"),+ class: cn("usesend-prose w-full"),
- className="bg-white rounded-md text-black p-8 unsend-editor light"+ className="bg-white rounded-md text-black p-8 usesend-editor light"

Also applies to: 108-109

🧹 Nitpick comments (41)
packages/sdk/README.md (3)

1-1: Brand casing: capitalize “UseSend” in the heading.
Keeps branding consistent with package/export names.

-# useSend SDK+# UseSend SDK

13-13: Add a short “Migrating from unsend” note after install commands.
Helps users upgrading from the old package.

 bun add usesend

+## Migrating from unsend
+
+If you previously used the unsend package:
+
+bash +npm uninstall unsend +npm install usesend +
+
+The SDK provides a temporary alias export for Unsend, but prefer:
+
+js +import { UseSend } from "usesend"; +


Also applies to: 19-19, 25-25, 31-31
---
`44-50`: **Tighten the example payload: verified-domain “from”, neutral copy, brand casing.**
Aligns with the prerequisites and avoids superlatives.
```diff
usesend.emails.send({
- to: "hello@acme.com",
- from: "hello@company.com",
- subject: "useSend email",
- html: "<p>useSend is the best open source product to send emails</p>",
- text: "useSend is the best open source product to send emails",
+ to: "user@example.com",
+ from: "no-reply@acme.com", // must be on a verified domain
+ subject: "UseSend email",
+ html: "<p>UseSend test email</p>",
+ text: "UseSend test email",
});
apps/web/src/lib/constants/example-codes.ts (1)

1-1: Prefer public export over deep import into package src

Importing from "@usesend/ui/src/code" couples the app to internals. If possible, use the public subpath or root export.

Apply one of these:

- import { CodeBlock } from "@usesend/ui/src/code";+ import { CodeBlock } from "@usesend/ui/code";

or

- import { CodeBlock } from "@usesend/ui/src/code";+ import { CodeBlock } from "@usesend/ui";

Please confirm which path is publicly exported from @usesend/ui. If neither is available, consider adding a subpath export.

apps/smtp-server/src/usage.js (1)

8-10: Avoid hardcoded SMTP creds; source from env with sane defaults.

Keeps secrets out of VCS and eases config across envs.

 auth: {
- user: "usesend",- pass: "us_123",+ user: process.env.SMTP_AUTH_USERNAME ?? "usesend",+ pass: process.env.SMTP_AUTH_PASSWORD ?? "us_123",
},

Optional: gate tls.rejectUnauthorized: false to non-production only.

apps/web/src/server/email-templates/TeamInviteEmail.tsx (2)

2-3: Consolidate imports from the same module.

Combine imports from "jsx-email" to follow the import grouping guideline.

-import { Container, Text } from "jsx-email";-import { render } from "jsx-email";+import { Container, Text, render } from "jsx-email";

17-23: Optional: centralize brand string.

To avoid future drift, read brand name from a single constant (e.g., ~/config/brand.ts) and reuse here and in other templates.

Also applies to: 25-26, 41-61

apps/web/src/server/email-templates/test.ts (1)

34-36: Optional: make the test runnable via pnpm tsx without CommonJS.

If the repo uses ESM, replace require.main guard with a small CLI wrapper or export the function and add a script entry.

apps/docs/get-started/set-up-docker.mdx (3)

15-18: Prefer Docker Compose v2 syntax in commands.

Docs use “Docker Compose”; match with docker compose CLI.

-docker-compose --env-file ./.env up -d+docker compose --env-file ./.env up -d

32-38: Pin image tags instead of floating latest.

Prevents breaking changes on future pulls.

-docker pull usesend/usesend+docker pull usesend/usesend:<version>-docker pull ghcr.io/usesend/usesend+docker pull ghcr.io/usesend/usesend:<version>

Also applies to: 42-44


46-61: Include API key env and pin image in run example.

Makes the example usable out-of-the-box and deterministic.

 docker run -d \
-p 3000:3000 \
-e NEXTAUTH_URL="<your-nextauth-url>" \
-e NEXTAUTH_SECRET="<your-nextauth-secret>" \
-e DATABASE_URL="<your-next-private-database-url>" \
-e REDIS_URL="<your-next-private-redis-url>" \
-e AWS_ACCESS_KEY="<your-next-private-aws-access-key-id>" \
-e AWS_SECRET_KEY="<your-next-private-aws-secret-access-key>" \
-e AWS_DEFAULT_REGION="<your-next-private-aws-region>" \
-e GITHUB_ID="<your-next-private-github-id>" \
-e GITHUB_SECRET="<your-next-private-github-secret>" \
+ -e USESEND_API_KEY="<your-usesend-api-key>" \- usesend/usesend+ usesend/usesend:<version>
apps/docs/get-started/local.mdx (2)

46-49: Grammar: use “set up” (verb), not “setup”.

Improves readability.

-To run useSend locally, you will need to setup the following:+To run useSend locally, you will need to set up the following:

46-49: Duplicate “Running useSend locally” section header—suggest restructure.

Rename the first to “Prerequisites” to avoid duplication; keep the second for actual run steps.

-## Running useSend locally+## Prerequisites

Also applies to: 144-146

apps/docs/get-started/smtp.mdx (1)

43-49: Minor content tweaks.

  • “to” should be the recipient; consider to: "recipient@example.com".
  • Consider consistent placeholder for API key and env var usage across examples.
- to: "sender@example.com",+ to: "recipient@example.com",- from: "hello@example.com",+ from: "hello@example.com",
apps/web/src/server/email-templates/components/EmailFooter.tsx (1)

29-38: Optional: open support link in a new tab (email-client permitting).

Not all clients respect it; harmless when ignored.

- <a- href={supportUrl}+ <a+ href={supportUrl}+ target="_blank"+ rel="noopener noreferrer"
style={{
color: "#000000",
textDecoration: "underline",
}}
apps/docs/guides/use-with-react-email.mdx (1)

51-66: Fix code fence language and async context

The snippet contains JSX; use tsx. Also avoid top-level await unless ESM—wrap in an async function or note ESM requirement.

-```ts+```tsx
import { UseSend } from "usesend";
import { render } from "@react-email/render";
import { Email } from "./email";
const usesend = new UseSend("us_your_usesend_api_key");
- const html = await render(<Email url="https://usesend.com" />);+async function main() {+ const html = await render(<Email url="https://usesend.com" />);-const response = await usesend.emails.send({+ const response = await usesend.emails.send({
to: "hello@usesend.com",
from: "hello@usesend.com",
subject: "useSend email",
html,
});
+}+main().catch(console.error);
docker/README.md (4)

21-23: Use Compose V2 syntax

Prefer docker compose (V2) over the deprecated docker-compose.

-docker-compose --env-file ./.env up -d+docker compose --env-file ./.env up -d

31-33: Minor wording

“Docker Hub” is the correct styling.

-If you prefer to host the useSend application on your container provider of choice, you can use the pre-built Docker image from DockerHub or GitHub's Package Registry. Note that you will need to provide your own database and SMTP host.+If you prefer to host the useSend application on your container provider of choice, you can use the pre-built Docker image from Docker Hub or GitHub's Package Registry. Note that you will need to provide your own database and SMTP host.

36-37: Pin image tags

Consider pinning images to a version tag to ensure reproducible deploys.

-docker pull usesend/usesend+docker pull usesend/usesend:latest
-docker pull ghcr.io/usesend/usesend+docker pull ghcr.io/usesend/usesend:latest
- usesend/usesend+ usesend/usesend:latest

Also applies to: 42-43, 59-59


54-56: Align AWS env var names or document custom ones
The code in apps/web/src/env.js reads process.env.AWS_ACCESS_KEY and process.env.AWS_SECRET_KEY, while the AWS SDK expects AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. If you want to adopt the SDK defaults, rename these env vars in both docker/README.md and apps/web/src/env.js; otherwise, explicitly note in the docs that AWS_ACCESS_KEY and AWS_SECRET_KEY are custom names.

packages/email-editor/src/editor.tsx (3)

22-22: Capitalize competitor brand

Use “Resend” (proper noun) in copy.

-<h3>useSend is the best open source resend alternative.</h3>+<h3>useSend is the best open source Resend alternative.</h3>

80-81: Type the ref

Provide a concrete ref type.

- const menuContainerRef = useRef(null);+ const menuContainerRef = useRef<HTMLDivElement | null>(null);

70-72: Use string[] over Array

Aligns with common TS style.

- variables?: Array<string>;+ variables?: string[];
apps/web/src/lib/constants/ses-errors.ts (1)

30-33: Polish wording in Suppressed message

Lowercase “Using” or reference docs explicitly.

- "useSend has suppressed sending to this address because it has a recent history of bouncing as an invalid address. To override the global suppression list, see Using the useSend account-level suppression list.",+ "useSend has suppressed sending to this address because it has a recent history of bouncing as an invalid address. To override the global suppression list, see the useSend account-level suppression list documentation.",
docker/prod/compose.yml (2)

6-6: Consider avoiding container_name in Compose.
Hard-coding container_name hinders scaling and parallel environments; letting Compose auto-name is more flexible.

Also applies to: 24-24, 34-34


46-49: Pin the application image and verify storage dependency.

  • Pin to a release tag or digest for reproducible deploys instead of :latest.
  • If the app actually uses MinIO, add it to depends_on to avoid race conditions at boot.

Example depends_on entry to extend existing block:

depends_on:
postgres:
condition: service_healthyredis:
condition: service_startedminio:
condition: service_started
apps/docs/get-started/nodejs.mdx (3)

3-3: Use canonical “Node.js” capitalization.

-description: "Send your mail using useSend in NodeJS"+description: "Send your mail using useSend in Node.js"

65-65: Fix spelling.

-## Adding contacts programatically+## Adding contacts programmatically

86-90: Clarify contactId in the update example.
Define or show how contactId is obtained to avoid confusion.

For example:

constcontact=awaitusesend.contacts.create("clzeydgeygff",{email: "a@b.com"});awaitusesend.contacts.update("clzeydgeygff",contact.id,{firstName: "Koushik"});
apps/docs/get-started/self-hosting.mdx (4)

99-99: Remove duplicate word.

-Your useSend instance is now live now.+Your useSend instance is now live.

103-104: Minor grammar/style tweaks for clarity.

-In order to send emails, you need to select a region in AWS. Use a region where your users are located / where useSend is hosted.+To send emails, select an AWS region. Use a region where your users are located or where useSend is hosted.

107-108: Fix phrasing.

-Once you log in to useSend, it will prompt you add SES configuration.+Once you log in to useSend, it will prompt you to add the SES configuration.

172-173: Use modern Docker Compose syntax.

-docker-compose up -d+docker compose up -d
apps/smtp-server/src/server.ts (3)

36-45: Surface HTTP status and clearer error detail.
Helps debugging upstream failures.

- if (!response.ok) {- const errorData = await response.text();+ if (!response.ok) {+ const status = response.status;+ const errorText = await response.text();
console.error(
- "useSend API error response: error:",- JSON.stringify(errorData, null, 4),+ "useSend API error response: status:",+ status,+ "error:",+ JSON.stringify(errorText, null, 4),
`\nemail data: ${emailDataText}`,
);
- throw new Error(- `Failed to send email: ${errorData || "Unknown error from server"}`,- );+ throw new Error(+ `Failed to send email (status ${status}): ${errorText || "Unknown error from server"}`,+ );
}

95-101: Ensure callback is invoked once and log before returning.

- sendEmailToUseSend(emailObject, session.user)- .then(() => callback())- .then(() => console.log("Email sent successfully to: ", emailObject.to))+ sendEmailToUseSend(emailObject, session.user)+ .then(() => {+ console.log("Email sent successfully to:", emailObject.to);+ callback();+ })
.catch((error) => {
console.error("Failed to send email:", error.message);
callback(error);
});

9-18: Add types and validate payload with Zod.
Strengthen typing and input validation per guidelines; avoid any.

Add near the top:

import{z}from"zod";constEmailPayloadSchema=z.object({to: z.string().min(1),from: z.string().min(1),subject: z.string().optional(),text: z.string().optional(),html: z.string().optional(),replyTo: z.string().optional(),});typeEmailPayload=z.infer<typeofEmailPayloadSchema>;

Then change the signature and validate:

asyncfunctionsendEmailToUseSend(emailData: EmailPayload,apiKey: string){constemail=EmailPayloadSchema.parse(emailData);// use `email` instead of `emailData` below}

Also applies to: 61-69

apps/web/src/server/mailer.ts (1)

10-16: Rename local var from “unsend” to “client” for clarity post-rebrand.

-let unsend: UseSend | undefined;+let client: UseSend | undefined;
const getClient = () => {
- if (!unsend) {- unsend = new UseSend(env.USESEND_API_KEY ?? env.UNSEND_API_KEY);+ if (!client) {+ client = new UseSend(env.USESEND_API_KEY ?? env.UNSEND_API_KEY);
}
- return unsend;+ return client;
};
apps/smtp-server/docker-compose.yml (4)

10-10: Avoid hardcoding the SaaS URL; allow self-hosted override.

Use Compose variable substitution with a sane default.

- USESEND_BASE_URL: "https://app.usesend.com" # your self hosted useSend instance url+ USESEND_BASE_URL: ${USESEND_BASE_URL:-https://app.usesend.com} # self‑host: set to your instance URL

13-14: Clarify that these are TLS certificate paths (not API keys).

The variable names include “API” but they appear to be the SMTP TLS key/cert. If renaming isn’t feasible due to image expectations, at least fix the comment to prevent operator confusion.

- # USESEND_API_KEY_PATH: "/certs/server.key"- # USESEND_API_CERT_PATH: "/certs/server.crt"+ # USESEND_API_KEY_PATH: "/certs/server.key" # TLS private key for SMTPS+ # USESEND_API_CERT_PATH: "/certs/server.crt" # TLS certificate for SMTPS

21-27: Publish TLS ports only when certs are configured (or provide an override).

465/2465 will fail without certs. Consider shipping a tls override file or document profiles.

Example override (docker-compose.tls.yml):

services:
smtp-server:
ports:
- "465:465"
- "2465:2465"environment:
USESEND_API_KEY_PATH: "/certs/server.key"USESEND_API_CERT_PATH: "/certs/server.crt"volumes:
- ./certs/server.key:/certs/server.key:ro
- ./certs/server.crt:/certs/server.crt:ro

Usage: docker compose -f docker-compose.yml -f docker-compose.tls.yml up -d


5-5: container_name limits scalability.

If you plan to scale or run multiple stacks on one host, avoid container_name and let Compose generate names.

- container_name: usesend-smtp-server+ # container_name: usesend-smtp-server # consider removing for scalability
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ca6e4c9 and 53e3ff9.

📒 Files selected for processing (39)
  • apps/docs/api-reference/introduction.mdx (2 hunks)
  • apps/docs/api-reference/openapi.json (2 hunks)
  • apps/docs/community-sdk/go.mdx (1 hunks)
  • apps/docs/get-started/create-aws-credentials.mdx (1 hunks)
  • apps/docs/get-started/local.mdx (6 hunks)
  • apps/docs/get-started/nodejs.mdx (3 hunks)
  • apps/docs/get-started/self-hosting.mdx (11 hunks)
  • apps/docs/get-started/set-up-docker.mdx (4 hunks)
  • apps/docs/get-started/smtp.mdx (3 hunks)
  • apps/docs/guides/use-with-react-email.mdx (4 hunks)
  • apps/docs/introduction.mdx (2 hunks)
  • apps/docs/mint.json (4 hunks)
  • apps/smtp-server/docker-compose.yml (1 hunks)
  • apps/smtp-server/src/server.ts (5 hunks)
  • apps/smtp-server/src/usage.js (2 hunks)
  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx (3 hunks)
  • apps/web/src/app/(dashboard)/settings/usage/usage.tsx (3 hunks)
  • apps/web/src/app/api/dev/email-preview/route.ts (1 hunks)
  • apps/web/src/app/api/to-html/route.ts (3 hunks)
  • apps/web/src/app/layout.tsx (2 hunks)
  • apps/web/src/app/login/login-page.tsx (4 hunks)
  • apps/web/src/app/unsubscribe/page.tsx (1 hunks)
  • apps/web/src/components/AppSideBar.tsx (3 hunks)
  • apps/web/src/components/FullScreenLoading.tsx (2 hunks)
  • apps/web/src/lib/constants/example-codes.ts (6 hunks)
  • apps/web/src/lib/constants/ses-errors.ts (2 hunks)
  • apps/web/src/server/aws/ses.ts (8 hunks)
  • apps/web/src/server/email-templates/OtpEmail.tsx (2 hunks)
  • apps/web/src/server/email-templates/TeamInviteEmail.tsx (2 hunks)
  • apps/web/src/server/email-templates/components/EmailFooter.tsx (2 hunks)
  • apps/web/src/server/email-templates/components/EmailHeader.tsx (2 hunks)
  • apps/web/src/server/email-templates/test.ts (3 hunks)
  • apps/web/src/server/mailer.ts (7 hunks)
  • apps/web/src/server/public-api/hono.ts (1 hunks)
  • apps/web/src/server/service/domain-service.ts (1 hunks)
  • docker/README.md (4 hunks)
  • docker/prod/compose.yml (5 hunks)
  • packages/email-editor/src/editor.tsx (3 hunks)
  • packages/sdk/README.md (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • apps/web/src/server/public-api/hono.ts
  • apps/docs/get-started/create-aws-credentials.mdx
  • apps/docs/community-sdk/go.mdx
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/web/src/components/AppSideBar.tsx
  • apps/web/src/app/api/to-html/route.ts
  • apps/web/src/server/aws/ses.ts
  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx
  • apps/web/src/components/FullScreenLoading.tsx
  • apps/web/src/app/(dashboard)/settings/usage/usage.tsx
  • apps/web/src/app/layout.tsx
  • apps/web/src/app/login/login-page.tsx
🧰 Additional context used
📓 Path-based instructions (10)
{apps,packages}/**/*.{js,jsx,ts,tsx,css,scss,md,mdx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier with the Tailwind plugin for code formatting

Files:

  • apps/web/src/server/email-templates/OtpEmail.tsx
  • apps/web/src/server/email-templates/TeamInviteEmail.tsx
  • apps/web/src/app/api/dev/email-preview/route.ts
  • apps/web/src/server/email-templates/components/EmailHeader.tsx
  • apps/web/src/server/service/domain-service.ts
  • apps/docs/introduction.mdx
  • apps/docs/api-reference/introduction.mdx
  • apps/web/src/server/email-templates/test.ts
  • apps/web/src/server/email-templates/components/EmailFooter.tsx
  • apps/docs/get-started/set-up-docker.mdx
  • apps/smtp-server/src/usage.js
  • apps/docs/get-started/smtp.mdx
  • packages/sdk/README.md
  • apps/web/src/lib/constants/ses-errors.ts
  • apps/docs/guides/use-with-react-email.mdx
  • apps/web/src/lib/constants/example-codes.ts
  • apps/docs/get-started/local.mdx
  • apps/docs/get-started/self-hosting.mdx
  • apps/docs/get-started/nodejs.mdx
  • apps/web/src/server/mailer.ts
  • packages/email-editor/src/editor.tsx
  • apps/smtp-server/src/server.ts
  • apps/web/src/app/unsubscribe/page.tsx
{apps,packages}/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{js,jsx,ts,tsx}: Group imports by source (internal/external) and alphabetize them
Use camelCase for variables and functions, PascalCase for components and classes
Use try/catch with specific error types for error handling

Files:

  • apps/web/src/server/email-templates/OtpEmail.tsx
  • apps/web/src/server/email-templates/TeamInviteEmail.tsx
  • apps/web/src/app/api/dev/email-preview/route.ts
  • apps/web/src/server/email-templates/components/EmailHeader.tsx
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/server/email-templates/test.ts
  • apps/web/src/server/email-templates/components/EmailFooter.tsx
  • apps/smtp-server/src/usage.js
  • apps/web/src/lib/constants/ses-errors.ts
  • apps/web/src/lib/constants/example-codes.ts
  • apps/web/src/server/mailer.ts
  • packages/email-editor/src/editor.tsx
  • apps/smtp-server/src/server.ts
  • apps/web/src/app/unsubscribe/page.tsx
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{ts,tsx}: Use strong typing in TypeScript, avoid any, and use Zod for validation
Follow Vercel style guides with strict TypeScript

Files:

  • apps/web/src/server/email-templates/OtpEmail.tsx
  • apps/web/src/server/email-templates/TeamInviteEmail.tsx
  • apps/web/src/app/api/dev/email-preview/route.ts
  • apps/web/src/server/email-templates/components/EmailHeader.tsx
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/server/email-templates/test.ts
  • apps/web/src/server/email-templates/components/EmailFooter.tsx
  • apps/web/src/lib/constants/ses-errors.ts
  • apps/web/src/lib/constants/example-codes.ts
  • apps/web/src/server/mailer.ts
  • packages/email-editor/src/editor.tsx
  • apps/smtp-server/src/server.ts
  • apps/web/src/app/unsubscribe/page.tsx
{apps,packages}/**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{jsx,tsx}: Use functional React components with hooks and group related hooks together
In React components, structure code with props at the top, hooks next, helper functions, then JSX

Files:

  • apps/web/src/server/email-templates/OtpEmail.tsx
  • apps/web/src/server/email-templates/TeamInviteEmail.tsx
  • apps/web/src/server/email-templates/components/EmailHeader.tsx
  • apps/web/src/server/email-templates/components/EmailFooter.tsx
  • packages/email-editor/src/editor.tsx
  • apps/web/src/app/unsubscribe/page.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use tRPC for internal API endpoints

In the web app, use the / alias for src imports (e.g., import { x } from "/utils/x")

Files:

  • apps/web/src/server/email-templates/OtpEmail.tsx
  • apps/web/src/server/email-templates/TeamInviteEmail.tsx
  • apps/web/src/app/api/dev/email-preview/route.ts
  • apps/web/src/server/email-templates/components/EmailHeader.tsx
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/server/email-templates/test.ts
  • apps/web/src/server/email-templates/components/EmailFooter.tsx
  • apps/web/src/lib/constants/ses-errors.ts
  • apps/web/src/lib/constants/example-codes.ts
  • apps/web/src/server/mailer.ts
  • apps/web/src/app/unsubscribe/page.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Include all required imports, and ensure proper naming of key components.

Files:

  • apps/web/src/server/email-templates/OtpEmail.tsx
  • apps/web/src/server/email-templates/TeamInviteEmail.tsx
  • apps/web/src/app/api/dev/email-preview/route.ts
  • apps/web/src/server/email-templates/components/EmailHeader.tsx
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/server/email-templates/test.ts
  • apps/web/src/server/email-templates/components/EmailFooter.tsx
  • apps/smtp-server/src/usage.js
  • apps/web/src/lib/constants/ses-errors.ts
  • apps/web/src/lib/constants/example-codes.ts
  • apps/web/src/server/mailer.ts
  • packages/email-editor/src/editor.tsx
  • apps/smtp-server/src/server.ts
  • apps/web/src/app/unsubscribe/page.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation and semicolons (enforced by Prettier) in TypeScript files

Files:

  • apps/web/src/server/email-templates/OtpEmail.tsx
  • apps/web/src/server/email-templates/TeamInviteEmail.tsx
  • apps/web/src/app/api/dev/email-preview/route.ts
  • apps/web/src/server/email-templates/components/EmailHeader.tsx
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/server/email-templates/test.ts
  • apps/web/src/server/email-templates/components/EmailFooter.tsx
  • apps/web/src/lib/constants/ses-errors.ts
  • apps/web/src/lib/constants/example-codes.ts
  • apps/web/src/server/mailer.ts
  • packages/email-editor/src/editor.tsx
  • apps/smtp-server/src/server.ts
  • apps/web/src/app/unsubscribe/page.tsx
{apps/web,apps/marketing,packages/ui,packages/email-editor}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Name React component files in PascalCase (e.g., AppSideBar.tsx)

Files:

  • apps/web/src/server/email-templates/OtpEmail.tsx
  • apps/web/src/server/email-templates/TeamInviteEmail.tsx
  • apps/web/src/server/email-templates/components/EmailHeader.tsx
  • apps/web/src/server/email-templates/components/EmailFooter.tsx
  • packages/email-editor/src/editor.tsx
  • apps/web/src/app/unsubscribe/page.tsx
**/*.{js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer TypeScript over JavaScript; new source files should use .ts/.tsx instead of .js/.jsx

Files:

  • apps/smtp-server/src/usage.js
apps/smtp-server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Hono for public API endpoints

Files:

  • apps/smtp-server/src/server.ts
🧠 Learnings (1)
📚 Learning: 2025-08-09T10:37:58.146Z
Learnt from: CR
PR: unsend-dev/unsend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-09T10:37:58.146Z
Learning: Applies to apps/smtp-server/**/*.{ts,tsx} : Use Hono for public API endpoints

Applied to files:

  • apps/web/src/server/email-templates/OtpEmail.tsx
  • apps/web/src/server/email-templates/test.ts
  • apps/smtp-server/src/usage.js
  • apps/docs/get-started/smtp.mdx
  • apps/web/src/server/mailer.ts
  • apps/smtp-server/src/server.ts
🧬 Code graph analysis (3)
apps/web/src/server/email-templates/TeamInviteEmail.tsx (1)
apps/web/src/server/email-templates/components/EmailLayout.tsx (1)
  • EmailLayout (16-77)
apps/web/src/app/api/dev/email-preview/route.ts (2)
apps/web/src/server/email-templates/TeamInviteEmail.tsx (1)
  • renderTeamInviteEmail (86-90)
apps/web/src/server/email-templates/index.ts (1)
  • renderTeamInviteEmail (2-2)
apps/web/src/server/mailer.ts (2)
packages/sdk/index.ts (2)
  • UseSend (1-1)
  • UseSend (2-2)
apps/web/src/env.js (2)
  • env (5-125)
  • env (5-125)
🪛 LanguageTool
apps/docs/introduction.mdx

[grammar] ~20-~20: There might be a mistake here.
Context: ...omains to send emails <Card title="Create API key" icon="key" ...

(QB_NEW_EN)

apps/docs/get-started/smtp.mdx

[grammar] ~11-~11: There might be a mistake here.
Context: ... the most out of this guide: - API Key - [Verified Domain](https://app.usesend.com...

(QB_NEW_EN)


[grammar] ~18-~18: There might be a mistake here.
Context: ...the following credentials: - Host:smtp.usesend.com - Port:465, 587, 2465...

(QB_NEW_EN)


[grammar] ~19-~19: There might be a mistake here.
Context: ...** 465, 587, 2465, or 2587 - Username:usesend - **Password:*...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ...`2465, or 2587``` - Username: ```usesend``` - Password: ```YOUR-API-KEY``` ## Examp...

(QB_NEW_EN)

apps/docs/get-started/local.mdx

[grammar] ~110-~110: Ensure spelling is correct
Context: ...new). This will allow you to sign in to useSend with your GitHub account. Add the home...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

apps/docs/get-started/self-hosting.mdx

[style] ~102-~102: Consider a more concise word here.
Context: ...e now. ## Step 3: Setting up a region In order to send emails, you need to select a regio...

(IN_ORDER_TO_PREMIUM)


[style] ~103-~103: For improved clarity, try using the conjunction “or” instead of a slash.
Context: ...e a region where your users are located / where useSend is hosted. If you're conf...

(QB_NEW_EN_SLASH_TO_OR)


[grammar] ~181-~181: There might be a mistake here.
Context: ...d(or your customSMTP_AUTH_USERNAME`) - Password: Your useSend API key - **Enc...

(QB_NEW_EN)


[grammar] ~182-~182: There might be a mistake here.
Context: ...E`) - Password: Your useSend API key - Encryption: STARTTLS (ports 25, 587, 2...

(QB_NEW_EN)

🪛 Checkov (3.2.334)
apps/docs/api-reference/openapi.json

[HIGH] 1-1482: Ensure that the global security field has rules defined

(CKV_OPENAPI_4)


[HIGH] 1-1482: Ensure that security operations is not empty.

(CKV_OPENAPI_5)

🪛 Biome (2.1.2)
apps/web/src/app/unsubscribe/page.tsx

[error] 40-40: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.

(lint/security/noBlankTarget)

🔇 Additional comments (34)
packages/sdk/README.md (2)

37-43: Use neutral self-hosted URL and positional base-URL constructor
The UseSend constructor signature is (apiKey?: string, baseUrl?: string). Swap out the SaaS domain for a neutral self-hosted example:

 // const usesend = new UseSend("us_12345", "https://app.usesend.com");
-// const usesend = new UseSend("us_12345", "https://app.usesend.com");+// const usesend = new UseSend("us_12345", "https://mail.your-company.example");

5-6: Update brand casing in README and verify dashboard URLs
In packages/sdk/README.md:

apps/web/src/lib/constants/example-codes.ts (3)

20-20: Verify SDK export shape (named vs default) to avoid runtime import errors

Confirm whether UseSend is a named or default export.

If default, switch to:

- import { UseSend } from "usesend";+ import UseSend from "usesend";

24-24: Confirm baseUrl example

Validate that "https://app.usesend.com" is the intended SDK base URL (vs a dedicated API hostname). If not required, consider removing the commented line to reduce confusion.


3-15: Confirm CodeBlock escapes content to prevent XSS in rendered examples

Since user-provided strings feed these snippets, ensure CodeBlock HTML-escapes and does not render dangerously.

If needed, I can help add tests to assert escaping.

apps/web/src/server/email-templates/components/EmailHeader.tsx (1)

18-21: LGTM on rebrand update.

Alt text reflects the new brand; no issues.

apps/web/src/server/email-templates/TeamInviteEmail.tsx (2)

25-25: Brand copy updated correctly.

Preview text now matches the useSend rebrand.


53-53: Body copy updated correctly.

Brand mention updated to “useSend”.

apps/web/src/app/api/dev/email-preview/route.ts (2)

21-23: Dev preview values updated to useSend.

Login URL and hostName look correct.


27-27: Invite URL updated to usesend.com.

Matches the new domain.

apps/web/src/server/email-templates/test.ts (1)

10-12: Test fixtures updated to useSend.

URLs and host identity reflect the rebrand.

Also applies to: 20-21

apps/docs/api-reference/openapi.json (2)

5-6: Title updated correctly.

Project name reflects “useSend API”.


9-10: Server URL updated to usesend.com.

Matches the new base URL.

apps/docs/api-reference/introduction.mdx (2)

8-14: Base URL/brand updated correctly.

REST note and base URL now point to usesend.com.


24-24: Developer Settings link updated correctly.

Points to app.usesend.com.

apps/web/src/server/email-templates/OtpEmail.tsx (1)

16-21: Default hostName rebrand looks good.

"useSend" default aligns with the PR’s branding. No functional issues.

apps/docs/get-started/set-up-docker.mdx (2)

2-4: Title/description rebrand reads well.

Consistent with the rest of the PR.


69-70: Closing paragraph LGTM.

Branding and call-to-action read cleanly.

apps/docs/get-started/local.mdx (5)

3-3: Description rebrand is correct.


9-9: Repo link updated correctly.


30-31: Minor: marketing bullet rebrand OK.


38-39: SDK bullet rebrand OK.


60-61: Fork/clone steps look correct.

Also applies to: 66-67

apps/docs/get-started/smtp.mdx (3)

3-4: Description rebrand is correct.


11-13: App URL updates look correct.


18-21: Confirm supported ports list. It’s unclear if ports 2465 and 2587 are officially supported for smtp.usesend.com; verify against the vendor’s documentation or support resources.

apps/web/src/server/email-templates/components/EmailFooter.tsx (1)

9-12: Defaults updated correctly.

Brand name and support URL align with the rebrand.

apps/docs/guides/use-with-react-email.mdx (1)

80-80: Fix grammar and correct example repository URLs

  • Change “Checkout” → “Check out”.
  • Update example links to use unsend-dev/unsend-js-examples for both JS and TS samples.
-Checkout this [example](https://github.com/usesend/unsend-js-examples/tree/main/react-email-js)+Check out this [example](https://github.com/unsend-dev/unsend-js-examples/tree/main/react-email-js)-Checkout this [example](https://github.com/usesend/unsend-js-examples/tree/main/react-email-ts)+Check out this [example](https://github.com/unsend-dev/unsend-js-examples/tree/main/react-email-ts)
apps/docs/mint.json (1)

117-119: Could you clarify which useSend you’re referring to? Please share the GitHub org or project URL (or tell me if it’s an npm package, web app, or owned by a specific org) so I can confirm the official X/Twitter handle and GitHub org.

apps/web/src/lib/constants/ses-errors.ts (1)

1-20: LGTM on branding replacements

String updates are consistent with the rebrand.

Also applies to: 22-46

docker/prod/compose.yml (2)

1-1: Rebrand naming LGTM.
Project name updated to usesend-prod is consistent with the PR objective.


65-67: SMTP defaults rebrand LGTM.
Host and user now point to usesend. Confirm if SMTP_PASSWORD/PORT need defaults in this file.

apps/smtp-server/docker-compose.yml (2)

1-1: Confirm Docker Compose v2 support
Run docker compose version in your local/dev/CI environments and ensure it reports v2.x (required for top-level name: in docker-compose.yml).


5-6: Pin the image; avoid using the floating latest tag.

Unpinned images make deployments non-reproducible and risky. Pin to a version or digest:

- image: usesend/smtp-proxy:latest+ image: usesend/smtp-proxy:vX.Y.Z # or digest: @sha256:…

Verify the chosen tag exists (e.g. via docker pull usesend/smtp-proxy:vX.Y.Z or the Docker Hub API) before merging.

Comment on lines +110 to 111
Next, [create a new GitHub App](https://github.com/settings/applications/new). This will allow you to sign in to useSend with your GitHub account.

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.

⚠️ Potential issue

GitHub OAuth vs GitHub App mismatch (fix link text).

The URL points to creating an OAuth App, not a GitHub App. NextAuth uses OAuth App Client ID/Secret.

-Next, [create a new GitHub App](https://github.com/settings/applications/new). This will allow you to sign in to useSend with your GitHub account.+Next, [create a new GitHub OAuth App](https://github.com/settings/applications/new). This will allow you to sign in to useSend with your GitHub account.
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Next, [create a new GitHub App](https://github.com/settings/applications/new). This will allow you to sign in to useSend with your GitHub account.
Next, [create a new GitHub OAuth App](https://github.com/settings/applications/new). This will allow you to sign in to useSend with your GitHub account.
🧰 Tools
🪛 LanguageTool

[grammar] ~110-~110: Ensure spelling is correct
Context: ...new). This will allow you to sign in to useSend with your GitHub account. Add the home...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
In apps/docs/get-started/local.mdx around lines 110–111, the link text says
"create a new GitHub App" but the URL targets the OAuth App creation page;
update the text to "create a new GitHub OAuth App" (or "create a new GitHub
OAuth App (Client ID/Secret)") so it matches the OAuth App URL and clarifies
that NextAuth requires an OAuth App Client ID/Secret; alternatively, if you
intended to reference GitHub Apps, change the URL to the GitHub App creation
page and add guidance for obtaining the appropriate credentials for NextAuth.

Comment on lines +31 to 37
host: "smtp.usesend.com",
port: 465,
secure: false,
auth: {
user: "unsend",
user: "usesend",
pass: "us_123",
},

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.

⚠️ Potential issue

Port 465 requires secure: true; don’t disable TLS verification.

Current example uses port 465 with secure: false and sets rejectUnauthorized: false, which is insecure.

- port: 465,- secure: false,+ port: 465,+ secure: true,
auth: {
- user: "usesend",- pass: "us_123",+ user: "usesend",+ pass: process.env.USESEND_API_KEY,
},
- tls:- rejectUnauthorized: false,+ // No custom TLS overrides needed in normal cases
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
host: "smtp.usesend.com",
port: 465,
secure: false,
auth: {
user: "unsend",
user: "usesend",
pass: "us_123",
},
host: "smtp.usesend.com",
port: 465,
secure: true,
auth: {
user: "usesend",
pass: process.env.USESEND_API_KEY,
},
// No custom TLS overrides needed in normal cases
🤖 Prompt for AI Agents
In apps/docs/get-started/smtp.mdx around lines 31 to 37, the example config uses
port 465 with secure: false and disables TLS verification; change secure to true
when using port 465 (or switch to port 587 if you need secure: false), and
remove or set rejectUnauthorized to true so TLS certificate verification is not
disabled; update the sample to either use port: 465 with secure: true and no
rejectUnauthorized override, or port: 587 with secure: false if demonstrating
STARTTLS.

---
title: Introduction
description: "Unsend is Open source alternative to Resend, Sendgrid, Mailgun and Postmark etc."
description: "useSend is open source alternative to Resend, Sendgrid, Mailgun and Postmark etc."

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.

⚠️ Potential issue

Fix grammar and brand capitalization in description.

Public-facing copy; tighten phrasing and casing.

-description: "useSend is open source alternative to Resend, Sendgrid, Mailgun and Postmark etc."+description: "useSend is an open-source alternative to Resend, SendGrid, Mailgun, and Postmark."
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: "useSend is open source alternative to Resend, Sendgrid, Mailgun and Postmark etc."
description: "useSend is an open-source alternative to Resend, SendGrid, Mailgun, and Postmark."
🤖 Prompt for AI Agents
In apps/docs/introduction.mdx around line 3, the description has grammar and
brand capitalization issues; update the string to use proper casing and
phrasing—change it to something like: "useSend is an open-source alternative to
Resend, SendGrid, Mailgun, Postmark, and others." (use hyphenated "open-source",
correct brand capitalization, include Oxford comma, and replace "etc." with "and
others").

environment:
SMTP_AUTH_USERNAME: "unsend" # can be anything, just use the same while sending emails
UNSEND_BASE_URL: "https://app.unsend.dev" # your self hosted unsend instance url
SMTP_AUTH_USERNAME: "usesend" # can be anything, just use the same while sending emails

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.

⚠️ Potential issue

SMTP AUTH likely incomplete; add a password and move creds out of the compose file.

Only setting a username risks misconfiguration (and accidental open relay if the proxy treats any password as valid). Recommend adding SMTP_AUTH_PASSWORD and loading both via env_file or secrets.

 environment:
- SMTP_AUTH_USERNAME: "usesend" # can be anything, just use the same while sending emails+ SMTP_AUTH_USERNAME: ${SMTP_AUTH_USERNAME:?set} # do not hardcode in VCS+ SMTP_AUTH_PASSWORD: ${SMTP_AUTH_PASSWORD:?set}+# env_file:+# - .env # store SMTP_AUTH_* here; do not commit
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
SMTP_AUTH_USERNAME: "usesend"# can be anything, just use the same while sending emails
environment:
SMTP_AUTH_USERNAME: ${SMTP_AUTH_USERNAME:?set} # do not hardcode in VCS
SMTP_AUTH_PASSWORD: ${SMTP_AUTH_PASSWORD:?set}
# env_file:
# - .env # store SMTP_AUTH_* here; do not commit
🤖 Prompt for AI Agents
In apps/smtp-server/docker-compose.yml around line 9, the compose only sets
SMTP_AUTH_USERNAME which risks misconfiguration and potential open relay; add
SMTP_AUTH_PASSWORD and move both SMTP_AUTH_USERNAME and SMTP_AUTH_PASSWORD out
of the compose file into an env_file or Docker secrets. Update the service to
read credentials from the env_file (or reference the secret names) and ensure
the env_file is excluded from version control (or secrets are managed by the
orchestrator) so credentials are not committed.

Comment on lines +19 to 24
async function sendEmailToUseSend(emailData: any, apiKey: string) {
try {
const apiEndpoint = "/api/v1/emails";
const url = new URL(apiEndpoint, UNSEND_BASE_URL); // Combine base URL with endpoint
console.log("Sending email to Unsend API at:", url.href); // Debug statement
const url = new URL(apiEndpoint, BASE_URL); // Combine base URL with endpoint
console.log("Sending email to useSend API at:", url.href); // Debug statement

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.

🛠️ Refactor suggestion

Add a request timeout to prevent hung SMTP sessions.
Without a timeout, fetch can hang indefinitely.

 async function sendEmailToUseSend(emailData: any, apiKey: string) {
@@
- const response = await fetch(url.href, {+ const controller = new AbortController();+ const timeout = setTimeout(() => controller.abort(), 10_000);+ const response = await fetch(url.href, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
- body: emailDataText,+ body: emailDataText,+ signal: controller.signal,
});
+ clearTimeout(timeout);

Also applies to: 27-35

🤖 Prompt for AI Agents
In apps/smtp-server/src/server.ts around lines 19-24 (and also apply same change
for lines 27-35), the fetch call to the useSend API has no timeout and can hang
indefinitely; add an AbortController, pass its signal to fetch, start a
setTimeout that calls controller.abort() after a reasonable timeout (e.g., 10s),
and clear the timer when the request completes; ensure you catch the abort error
specifically (identify it as an AbortError) and handle/propagate it
appropriately so stalled SMTP sessions are terminated and resources are cleaned
up.

Comment on lines 37 to 42
<div className=" fixed bottom-10 p-4">
<p>
Powered by{" "}
<a href="https://unsend.dev" className="font-bold" target="_blank">
Unsend
<a href="https://usesend.com" className="font-bold" target="_blank">
useSend
</a>

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.

⚠️ Potential issue

Add rel="noopener noreferrer" for external link security.

Opening in a new tab without rel leaks window.opener and is a security risk.

- <a href="https://usesend.com" className="font-bold" target="_blank">+ <a+ href="https://usesend.com"+ className="font-bold"+ target="_blank"+ rel="noopener noreferrer"+ >
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<divclassName=" fixed bottom-10 p-4">
<p>
Poweredby{" "}
<ahref="https://unsend.dev"className="font-bold"target="_blank">
Unsend
<ahref="https://usesend.com"className="font-bold"target="_blank">
useSend
</a>
<divclassName=" fixed bottom-10 p-4">
<p>
Poweredby{" "}
<a
href="https://usesend.com"
className="font-bold"
target="_blank"
rel="noopener noreferrer"
>
useSend
</a>
🧰 Tools
🪛 Biome (2.1.2)

[error] 40-40: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.

(lint/security/noBlankTarget)

🤖 Prompt for AI Agents
In apps/web/src/app/unsubscribe/page.tsx around lines 37 to 42, the external
anchor uses target="_blank" but lacks rel="noopener noreferrer"; update the
anchor to add rel="noopener noreferrer" to prevent window.opener leakage and
improve security when opening the external link in a new tab.

Comment on lines +129 to 132
code: `curl -X POST https://app.usesend.com/api/v1/emails \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer us_12345" \\
-d '{"to": "${to}", "from": "${from}", "subject": "${subject}", "text": "${body}", "html": "${bodyHtml}"}'`,

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.

🛠️ Refactor suggestion

cURL: make payload quoting robust

Single-quoted -d breaks if values contain single quotes. Use a heredoc to avoid escaping hell.

-code: `curl -X POST https://app.usesend.com/api/v1/emails \\--H "Content-Type: application/json" \\--H "Authorization: Bearer us_12345" \\--d '{"to": "${to}", "from": "${from}", "subject": "${subject}", "text": "${body}", "html": "${bodyHtml}"}'`,+code: `curl -X POST https://app.usesend.com/api/v1/emails \\+-H "Content-Type: application/json" \\+-H "Authorization: Bearer us_12345" \\+--data-binary @- <<'JSON'+{+ "to": ${JSON.stringify(to)},+ "from": ${JSON.stringify(from)},+ "subject": ${JSON.stringify(subject)},+ "text": ${JSON.stringify(body)},+ "html": ${JSON.stringify(bodyHtml)}+}+JSON`,

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/src/lib/constants/example-codes.ts around lines 129 to 132, the curl
example uses -d with single quotes which breaks if any value contains single
quotes; replace the -d single-quoted payload with a heredoc so quoting is robust
and variables expand correctly (use --data-binary @- <<EOF followed by the JSON
body with ${to}, ${from}, etc. on the next lines, then EOF) to avoid escaping
issues and preserve full payload content.

Comment on lines 41 to 45
if (!domain) {
throw new UnsendApiError({
code: "BAD_REQUEST",
message: `Domain: ${fromDomain} of from email is wrong. Use the domain verified by unsend`,
message: `Domain: ${fromDomain} of from email is wrong. Use the domain verified by useSend`,
});

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.

⚠️ Potential issue

Clarify error message wording.

Current text is awkward; propose clearer, user-friendly phrasing.

- message: `Domain: ${fromDomain} of from email is wrong. Use the domain verified by useSend`,+ message: `The domain in the From email (${fromDomain}) is invalid. Use a domain verified in useSend.`,
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(!domain){
thrownewUnsendApiError({
code: "BAD_REQUEST",
message: `Domain: ${fromDomain} of from email is wrong. Use the domain verified by unsend`,
message: `Domain: ${fromDomain} of from email is wrong. Use the domain verified by useSend`,
});
if(!domain){
thrownewUnsendApiError({
code: "BAD_REQUEST",
message: `The domain in the From email (${fromDomain}) is invalid. Use a domain verified in useSend.`,
});
🤖 Prompt for AI Agents
In apps/web/src/server/service/domain-service.ts around lines 41 to 45, the
thrown UnsendApiError message is awkwardly worded; replace it with a clearer,
user-friendly sentence such as: indicate that the from-email domain is not
verified and instruct the user to use a domain verified with useSend, including
the problematic domain value for context. Ensure the new message reads
succinctly (e.g., "The from email domain (<domain>) is not verified. Please use
a domain verified with useSend.") and keep the error code as "BAD_REQUEST".

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/server/service/domain-service.ts (1)

37-39: Fix invalid findUnique usage

In apps/web/src/server/service/domain-service.ts (lines 37–39), findUnique only accepts a single unique field or a defined composite key. Since name is already unique, drop teamId from the where clause:

- const domain = await db.domain.findUnique({- where: { name: fromDomain, teamId },- });+ const domain = await db.domain.findUnique({+ where: { name: fromDomain },+ });

If domains should be scoped per team, update your Prisma schema to add a composite unique and then query by that compound key:

model Domain {
- name String @unique+ name String
teamId Int
+ @@unique([teamId, name])
}
awaitdb.domain.findUnique({where: {teamId_name: { teamId,name: fromDomain}},});
♻️ Duplicate comments (1)
apps/web/src/server/service/domain-service.ts (1)

41-45: Reword error message for clarity and brand consistency.

This retains context while reading naturally and matches the “useSend” brand.

- message: `Domain: ${fromDomain} of from email is wrong. Use the domain verified by useSend`,+ message: `The domain in the From email (${fromDomain}) is invalid. Use a domain verified in useSend.`,
🧹 Nitpick comments (4)
apps/web/prisma/migrations/20250901091500_add_dkim_selector/migration.sql (1)

2-8: Confirm selector backfill and keep single source of truth for defaults.

  • Backfilling existing rows to 'unsend' and defaulting new rows to 'usesend' makes sense. Please confirm production domains indeed use 'unsend' today so this doesn’t drift from SES. Also, ensure this default is defined in one place (env/config) and reused by app code and SES layer.

Optionally, add a CHECK to prevent invalid selector values:

 ALTER TABLE "Domain" ALTER COLUMN "dkimSelector" SET DEFAULT 'usesend';
++-- Optional: enforce DKIM selector label constraints (1–63 chars, RFC 1035-ish)+ALTER TABLE "Domain"+ ADD CONSTRAINT "Domain_dkimSelector_format_chk"+ CHECK (+ "dkimSelector" IS NULL+ OR "dkimSelector" ~ '^[A-Za-z0-9]([A-Za-z0-9-_]{0,61}[A-Za-z0-9])?$'+ );
apps/web/prisma/schema.prisma (1)

184-185: LGTM: dkimSelector added with default.

Matches the migration intent.

To constrain selector length at the DB level via Prisma, consider:

- dkimSelector String? @default("usesend")+ dkimSelector String? @db.VarChar(63) @default("usesend")
apps/web/src/server/service/domain-service.ts (2)

88-91: Avoid hardcoding the DKIM selector; read from config/env to prevent drift.

Keep this default aligned with the DB default and SES default.

- const dkimSelector = "usesend";+ const dkimSelector =+ process.env.DKIM_SELECTOR_DEFAULT?.trim() || "usesend";

92-101: Propagate selector consistently across layers.

Good: passing selector to SES and persisting it. Ensure SES default matches this value to avoid surprises if the arg is omitted elsewhere.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 53e3ff9 and 493f4b6.

📒 Files selected for processing (7)
  • apps/web/prisma/migrations/20250901091500_add_dkim_selector/migration.sql (1 hunks)
  • apps/web/prisma/schema.prisma (1 hunks)
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx (8 hunks)
  • apps/web/src/components/AppSideBar.tsx (4 hunks)
  • apps/web/src/server/aws/ses.ts (8 hunks)
  • apps/web/src/server/mailer.ts (6 hunks)
  • apps/web/src/server/service/domain-service.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/src/server/aws/ses.ts
  • apps/web/src/server/mailer.ts
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
  • apps/web/src/components/AppSideBar.tsx
🧰 Additional context used
📓 Path-based instructions (6)
{apps,packages}/**/*.{js,jsx,ts,tsx,css,scss,md,mdx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier with the Tailwind plugin for code formatting

Files:

  • apps/web/src/server/service/domain-service.ts
{apps,packages}/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{js,jsx,ts,tsx}: Group imports by source (internal/external) and alphabetize them
Use camelCase for variables and functions, PascalCase for components and classes
Use try/catch with specific error types for error handling

Files:

  • apps/web/src/server/service/domain-service.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{ts,tsx}: Use strong typing in TypeScript, avoid any, and use Zod for validation
Follow Vercel style guides with strict TypeScript

Files:

  • apps/web/src/server/service/domain-service.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use tRPC for internal API endpoints

In the web app, use the / alias for src imports (e.g., import { x } from "/utils/x")

Files:

  • apps/web/src/server/service/domain-service.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Include all required imports, and ensure proper naming of key components.

Files:

  • apps/web/src/server/service/domain-service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation and semicolons (enforced by Prettier) in TypeScript files

Files:

  • apps/web/src/server/service/domain-service.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 3

🧹 Nitpick comments (4)
packages/tailwind-config/tailwind.config.ts (1)

8-31: Align Tailwind fonts with Next.js variables; remove duplicate Menlo.

Prepend the CSS vars so font-sans/font-mono pick up Next/font, and drop the repeated "Menlo".

 theme: {
- fontFamily: {- sans: [- "Inter",+ fontFamily: {+ sans: [+ "var(--font-sans)",+ "Inter",
"ui-sans-serif",
"system-ui",
"sans-serif",
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji",
],
- mono: [- "JetBrains Mono",- "Menlo",+ mono: [+ "var(--font-mono)",+ "JetBrains Mono",
"ui-monospace",
"SFMono-Regular",
- "Menlo",+ "Menlo",
"Monaco",
"Consolas",
"Liberation Mono",
"Courier New",
"monospace",
],
},
apps/web/src/app/layout.tsx (1)

34-36: Make font var usage effective; remove duplicate bg class (optional).

  • If you adopt the Tailwind var mapping (see tailwind-config comment), this stays as-is.
  • Minor: background is set on both html and body; keep one.
- <body- className={`font-sans ${inter.variable} ${jetbrainsMono.variable} app bg-sidebar-background`}- >+ <body className={`font-sans ${inter.variable} ${jetbrainsMono.variable} app`}>
apps/web/src/app/login/login-page.tsx (2)

3-3: Avoid deep /src imports from @usesend/ui; use public entrypoints and keep import style consistent.

Deep-linking into package internals is brittle and can break bundling/TS path exports. Prefer public entrypoints and group/alphabetize imports per guidelines.

Example diff (adjust to actual exports):

-import { Button } from "@usesend/ui/src/button";+import { Button } from "@usesend/ui/button";-} from "@usesend/ui/src/form";+} from "@usesend/ui/form";-} from "@usesend/ui/src/input-otp";+} from "@usesend/ui/input-otp";-import { Input } from "@usesend/ui/src/input";+import { Input } from "@usesend/ui/input";-import Spinner from "@usesend/ui/src/spinner";+import Spinner from "@usesend/ui/spinner";

If the package re-exports from the root, an even cleaner option:

import{Button,Form,FormControl,FormDescription,FormField,FormItem,FormMessage,Input,InputOTP,InputOTPGroup,InputOTPSlot,REGEXP_ONLY_DIGITS_AND_CHARS,Spinner,}from"@usesend/ui";

Also applies to: 17-17, 23-24, 26-26


117-119: Use absolute callback URL here as well for consistency with OTP flow.

- const callbackUrl = inviteId- ? `/join-team?inviteId=${inviteId}`- : "/dashboard";+ const callbackUrl = inviteId+ ? new URL(`/join-team?inviteId=${inviteId}`, window.location.origin).toString()+ : new URL("/dashboard", window.location.origin).toString();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 493f4b6 and 7f45355.

⛔ Files ignored due to path filters (14)
  • apps/web/public/Logo-rounded.png is excluded by !**/*.png
  • apps/web/public/favicon.ico is excluded by !**/*.ico
  • apps/web/public/favicon_io/android-chrome-192x192.png is excluded by !**/*.png
  • apps/web/public/favicon_io/android-chrome-512x512.png is excluded by !**/*.png
  • apps/web/public/favicon_io/apple-touch-icon.png is excluded by !**/*.png
  • apps/web/public/favicon_io/favicon-16x16.png is excluded by !**/*.png
  • apps/web/public/favicon_io/favicon-32x32.png is excluded by !**/*.png
  • apps/web/public/favicon_io/favicon.ico is excluded by !**/*.ico
  • apps/web/public/logo-dark.png is excluded by !**/*.png
  • apps/web/public/logo-dark.svg is excluded by !**/*.svg
  • apps/web/public/logo-full-wordmark.svg is excluded by !**/*.svg
  • apps/web/public/logo-light.png is excluded by !**/*.png
  • apps/web/public/logo-light.svg is excluded by !**/*.svg
  • apps/web/public/logo-squircle.png is excluded by !**/*.png
📒 Files selected for processing (5)
  • apps/web/src/app/layout.tsx (3 hunks)
  • apps/web/src/app/login/login-page.tsx (5 hunks)
  • apps/web/src/components/AppSideBar.tsx (4 hunks)
  • apps/web/src/components/FullScreenLoading.tsx (1 hunks)
  • packages/tailwind-config/tailwind.config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/components/FullScreenLoading.tsx
  • apps/web/src/components/AppSideBar.tsx
🧰 Additional context used
📓 Path-based instructions (8)
{apps,packages}/**/*.{js,jsx,ts,tsx,css,scss,md,mdx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier with the Tailwind plugin for code formatting

Files:

  • packages/tailwind-config/tailwind.config.ts
  • apps/web/src/app/layout.tsx
  • apps/web/src/app/login/login-page.tsx
{apps,packages}/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{js,jsx,ts,tsx}: Group imports by source (internal/external) and alphabetize them
Use camelCase for variables and functions, PascalCase for components and classes
Use try/catch with specific error types for error handling

Files:

  • packages/tailwind-config/tailwind.config.ts
  • apps/web/src/app/layout.tsx
  • apps/web/src/app/login/login-page.tsx
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{ts,tsx}: Use strong typing in TypeScript, avoid any, and use Zod for validation
Follow Vercel style guides with strict TypeScript

Files:

  • packages/tailwind-config/tailwind.config.ts
  • apps/web/src/app/layout.tsx
  • apps/web/src/app/login/login-page.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Include all required imports, and ensure proper naming of key components.

Files:

  • packages/tailwind-config/tailwind.config.ts
  • apps/web/src/app/layout.tsx
  • apps/web/src/app/login/login-page.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation and semicolons (enforced by Prettier) in TypeScript files

Files:

  • packages/tailwind-config/tailwind.config.ts
  • apps/web/src/app/layout.tsx
  • apps/web/src/app/login/login-page.tsx
{apps,packages}/**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{jsx,tsx}: Use functional React components with hooks and group related hooks together
In React components, structure code with props at the top, hooks next, helper functions, then JSX

Files:

  • apps/web/src/app/layout.tsx
  • apps/web/src/app/login/login-page.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use tRPC for internal API endpoints

In the web app, use the / alias for src imports (e.g., import { x } from "/utils/x")

Files:

  • apps/web/src/app/layout.tsx
  • apps/web/src/app/login/login-page.tsx
{apps/web,apps/marketing,packages/ui,packages/email-editor}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Name React component files in PascalCase (e.g., AppSideBar.tsx)

Files:

  • apps/web/src/app/layout.tsx
  • apps/web/src/app/login/login-page.tsx
🔇 Additional comments (6)
apps/web/src/app/layout.tsx (3)

1-1: Rebrand import LGTM.
Path switch to @usesend is correct.


16-20: JetBrains Mono setup LGTM.
Matches Tailwind config once vars are wired as suggested.


4-6: Combine next/font imports, retain deep Toaster import until re-exported

  • Merge the two next/font/google imports:
    -import { Inter } from "next/font/google";-import { JetBrains_Mono } from "next/font/google";+import { Inter, JetBrains_Mono } from "next/font/google";
  • The Toaster component isn't exported from @usesend/ui’s public entrypoint (packages/ui/index.ts), so import { Toaster } from "@usesend/ui" will break; continue using the deep import (@usesend/ui/src/toaster) or add a re-export in the UI package.

Likely an incorrect or invalid review comment.

apps/web/src/app/login/login-page.tsx (3)

137-137: Branding copy LGTM.


140-140: CTA copy LGTM.


129-130: Verified asset path exists; no changes required.

Comment on lines +22 to 24
title: "useSend",
description: "Open source email platoform",
icons: [{ rel: "icon", url: "/favicon.ico" }],

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.

⚠️ Potential issue

Fix typo in description.
User-facing copy: “platoform” → “platform”.

- description: "Open source email platoform",+ description: "Open source email platform",
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
title: "useSend",
description: "Open source email platoform",
icons: [{rel: "icon",url: "/favicon.ico"}],
title: "useSend",
description: "Open source email platform",
icons: [{rel: "icon",url: "/favicon.ico"}],
🤖 Prompt for AI Agents
In apps/web/src/app/layout.tsx around lines 22 to 24, the description string
contains a typo ("platoform"); update the value to the correct spelling
"platform" so the metadata reads: description: "Open source email platform".
Ensure only the spelling change is made and punctuation/formatting remains
consistent.

import Spinner from "@usesend/ui/src/spinner";
import Link from "next/link";
import { useTheme } from "@unsend/ui";
import { useTheme } from "@usesend/ui";

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.

🛠️ Refactor suggestion

Remove unused theme hook.

useTheme/resolvedTheme is unused and may fail strict TS/lint.

-import { useTheme } from "@usesend/ui";

Additionally, remove the unused variable later in the file:

- const { resolvedTheme } = useTheme();+ // removed unused theme hook
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import{useTheme}from"@usesend/ui";
// apps/web/src/app/login/login-page.tsx
// --- Import section ---
importReactfrom"react";
import{useRouter}from"next/router";
// import { useTheme } from "@usesend/ui"; // removed unused hook
exportdefaultfunctionLoginPage(){
constrouter=useRouter();
// removed unused theme hook
// ...rest of component logic and JSX
}
🤖 Prompt for AI Agents
In apps/web/src/app/login/login-page.tsx at line 28, the imported useTheme from
"@usesend/ui" is unused (and resolvedTheme variable later in the file is unused)
which can fail strict TS/lint; remove the unused import line and delete the
corresponding resolvedTheme (and any associated unused destructuring) where it
is declared later in the file so no unused variables remain and adjust any code
that referenced them if necessary.

Comment on lines +97 to +99
const finalCallbackUrl = inviteId
? `/join-team?inviteId=${inviteId}`
: `${callbackUrl}/dashboard`;

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.

⚠️ Potential issue

Normalize callback URL construction; also drop PII logging.

Mixing relative and absolute callback URLs can cause NextAuth host mismatches. Build absolute URLs consistently. Also remove the console.log(email) above to avoid leaking PII.

- const finalCallbackUrl = inviteId- ? `/join-team?inviteId=${inviteId}`- : `${callbackUrl}/dashboard`;+ const finalCallbackUrl = inviteId+ ? new URL(`/join-team?inviteId=${inviteId}`, window.location.origin).toString()+ : new URL("/dashboard", window.location.origin).toString();

Remove the debug log near this block:

- console.log("email", email);+ // removed debug log
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constfinalCallbackUrl=inviteId
? `/join-team?inviteId=${inviteId}`
: `${callbackUrl}/dashboard`;
// removed debug log
constfinalCallbackUrl=inviteId
? newURL(`/join-team?inviteId=${inviteId}`,window.location.origin).toString()
: newURL("/dashboard",window.location.origin).toString();
🤖 Prompt for AI Agents
In apps/web/src/app/login/login-page.tsx around lines 97 to 99, the callback URL
is being built as a mix of relative and absolute paths which can cause NextAuth
host mismatches and there's a nearby debug console.log(email) that leaks PII;
replace the relative construction with a consistently absolute URL (use request
origin or process.env.NEXTAUTH_URL as the base, then append either
/join-team?inviteId=... or /dashboard) and remove the console.log(email)
statement so no email is logged.

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx (1)

139-141: dangerouslySetInnerHTML without sanitization risks XSS.

If campaign.html contains untrusted HTML, this can execute scripts.

Option: sanitize on the client with isomorphic-dompurify.

- <div dangerouslySetInnerHTML={{ __html: campaign.html ?? "" }} />+ <div+ dangerouslySetInnerHTML={{+ __html: DOMPurify.sanitize(campaign.html ?? "", {+ USE_PROFILES: { html: true },+ }),+ }}+ />

Add at top:

importDOMPurifyfrom"isomorphic-dompurify";

Or ensure server-side sanitization before persisting/serving the HTML.

apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx (1)

283-285: Update brand text: “Unsend” → “UseSend”.

User-facing copy should reflect the rebrand.

- Unsend adds a tracking pixel to every email you send. This allows you+ UseSend adds a tracking pixel to every email you send. This allows you
🧹 Nitpick comments (29)
apps/web/src/components/settings/AddSesSettings.tsx (3)

12-19: Avoid deep imports from @usesend/ui; prefer public/barrel exports.

Importing from "/src/*" couples the app to package internals and can break when the UI package changes its build layout. Switch to stable entry points and (per guidelines) group/alpha-sort imports.

-} from "@usesend/ui/src/form";+} from "@usesend/ui/form";-import { Input } from "@usesend/ui/src/input";+import { Input } from "@usesend/ui/input";-import { Button } from "@usesend/ui/src/button";+import { Button } from "@usesend/ui/button";-import Spinner from "@usesend/ui/src/spinner";+import Spinner from "@usesend/ui/spinner";-import { toast } from "@usesend/ui/src/toaster";+import { toast } from "@usesend/ui/toaster";

If the UI package only exposes a single root entry, use @usesend/ui for all of the above. Also verify whether Spinner is a default or named export in the current UI package.


24-24: Move HTTPS/localhost checks into the Zod schema and fix message casing.

Centralizing validation in the schema reduces imperative form logic and ensures consistent error messages. Also change “url” → “URL”.

- usesendUrl: z.string().url(),+ usesendUrl: z+ .string()+ .trim()+ .url("Enter a valid URL")+ .superRefine((val, ctx) => {+ const localhost = isLocalhost();+ if (!localhost && !val.startsWith("https://")) {+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "URL must start with https://" });+ }+ if (!localhost && val.includes("localhost")) {+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "URL must be a valid URL" });+ }+ }),
- const localhost = isLocalhost();-- if (!data.usesendUrl.startsWith("https://") && !localhost) {- form.setError("usesendUrl", {- message: "URL must start with https://",- });- return;- }-- if (data.usesendUrl.includes("localhost") && !localhost) {- form.setError("usesendUrl", {- message: "URL must be a valid url",- });- return;- }+ // URL validation handled by Zod schema above.

Also applies to: 68-80


82-101: Payload mapping looks good; confirm server contract and consider mutateAsync.

Mapping usesendUrl → unsendUrl is correct pending server migration. Please confirm the server still expects unsendUrl; plan to flip this once backend renames land. Optionally simplify flow with mutateAsync and try/catch.

- addSesSettings.mutate(- {- region: data.region,- unsendUrl: data.usesendUrl,- sendRate: data.sendRate,- transactionalQuota: data.transactionalQuota,- },- {- onSuccess: () => {- utils.admin.invalidate();- onSuccess?.();- },- onError: (e) => {- toast.error("Failed to create", {- description: e.message,- });- },- }- );+ try {+ await addSesSettings.mutateAsync({+ region: data.region,+ unsendUrl: data.usesendUrl,+ sendRate: data.sendRate,+ transactionalQuota: data.transactionalQuota,+ });+ utils.admin.invalidate();+ onSuccess?.();+ } catch (e: any) {+ toast.error("Failed to create", { description: e.message });+ }
packages/ui/src/sheet.tsx (3)

42-58: Left/right variants are visually inconsistent; align sizing/offsets.

Right uses 95% height + vertical centering and a 32px gutter; left uses full height and no gutter. Suggest mirroring right for left to keep UX consistent.

 side: {
top: "inset-x-0 top-0",
bottom: "inset-x-0 bottom-0",
- left: "inset-y-0 left-0 h-full w-3/4 sm:max-w-sm",+ left:+ "inset-y-0 left-[32px] h-[95%] my-auto w-3/4 sm:max-w-sm",
right:
"inset-y-0 right-[32px] h-[95%] my-auto w-3/4 sm:max-w-sm",
},

77-80: Animate vertically for top/bottom sheets.

Current motion only slides on the X axis; top/bottom should use Y to avoid odd lateral motion.

- initial={{ x: side === "left" ? "-50%" : "50%" }}- animate={{ x: 0 }}- exit={{ x: side === "left" ? "-50%" : "50%" }}+ initial={+ side === "left" || side === "right"+ ? { x: side === "left" ? "-50%" : "50%" }+ : { y: side === "top" ? "-50%" : "50%" }+ }+ animate={side === "left" || side === "right" ? { x: 0 } : { y: 0 }}+ exit={+ side === "left" || side === "right"+ ? { x: side === "left" ? "-50%" : "50%" }+ : { y: side === "top" ? "-50%" : "50%" }+ }

55-56: Avoid duplicate defaults for “side”.

You set side: "right" in cva defaultVariants and again in the prop default. Keep a single source of truth.

->(({ side = "right", className, children, ...props }, ref) => (+>(({ side, className, children, ...props }, ref) => (

Also applies to: 67-67

apps/web/src/app/(dashboard)/domains/page.tsx (1)

5-5: Group and alphabetize imports (external first).

Move @usesend/ui above local imports per guidelines.

-import DomainsList from "./domain-list";-import AddDomain from "./add-domain";-import { H1 } from "@usesend/ui";+import { H1 } from "@usesend/ui";+import AddDomain from "./add-domain";+import DomainsList from "./domain-list";
apps/web/src/app/(dashboard)/admin/page.tsx (2)

5-5: Group and alphabetize imports (external first).

Place @usesend/ui import before locals.

-import AddSesConfiguration from "./add-ses-configuration";-import SesConfigurations from "./ses-configurations";-import { H1 } from "@usesend/ui";+import { H1 } from "@usesend/ui";+import AddSesConfiguration from "./add-ses-configuration";+import SesConfigurations from "./ses-configurations";

7-7: Rename component to match the page (admin).

The default export is misnamed ApiKeysPage; prefer AdminPage for clarity.

-export default function ApiKeysPage() {+export default function AdminPage() {
apps/web/src/app/(dashboard)/contacts/page.tsx (1)

5-5: Group and alphabetize imports (external first).

Reorder imports to meet the project style.

-import AddContactBook from "./add-contact-book";-import ContactBooksList from "./contact-books-list";-import { H1 } from "@usesend/ui";+import { H1 } from "@usesend/ui";+import AddContactBook from "./add-contact-book";+import ContactBooksList from "./contact-books-list";
apps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsx (1)

5-5: Group and alphabetize imports (external first).

Place @usesend/ui import before local modules.

-import AddApiKey from "./add-api-key";-import ApiList from "./api-list";-import { H1 } from "@usesend/ui";+import { H1 } from "@usesend/ui";+import AddApiKey from "./add-api-key";+import ApiList from "./api-list";
apps/web/src/app/(dashboard)/dev-settings/page.tsx (1)

3-5: Reorder imports: external before internal, then alphabetize within groups.

Aligns with repo guidelines. Put @usesend/ui first, then local imports.

-import AddApiKey from "./api-keys/add-api-key";-import ApiList from "./api-keys/api-list";-import { H1 } from "@usesend/ui";+import { H1 } from "@usesend/ui";+import AddApiKey from "./api-keys/add-api-key";+import ApiList from "./api-keys/api-list";
apps/web/src/app/(dashboard)/emails/page.tsx (1)

3-4: Reorder imports per guidelines (external first).

-import EmailList from "./email-list";-import { H1 } from "@usesend/ui";+import { H1 } from "@usesend/ui";+import EmailList from "./email-list";
apps/web/src/app/(dashboard)/templates/page.tsx (1)

3-5: Import grouping/order tweak.

External first; then internal, alphabetized.

-import TemplateList from "./template-list";-import CreateTemplate from "./create-template";-import { H1 } from "@usesend/ui";+import { H1 } from "@usesend/ui";+import CreateTemplate from "./create-template";+import TemplateList from "./template-list";
apps/web/src/app/(dashboard)/campaigns/page.tsx (2)

3-5: Fix import order (external before internal).

-import CampaignList from "./campaign-list";-import CreateCampaign from "./create-campaign";-import { H1 } from "@usesend/ui";+import { H1 } from "@usesend/ui";+import CampaignList from "./campaign-list";+import CreateCampaign from "./create-campaign";

7-7: Rename component to match the route/page (Campaigns).

Currently ContactsPage—rename to CampaignsPage for clarity and devtools readability.

-export default function ContactsPage() {+export default function CampaignsPage() {
apps/web/src/app/(dashboard)/dashboard/page.tsx (2)

3-8: Reorder imports per repo guidelines (external first, then internal) and alphabetize.

Move the @usesend/ui import above local “./” and “~/” imports.

Apply:

+import { H1 } from "@usesend/ui";
import EmailChart from "./email-chart";
import DashboardFilters from "./dashboard-filters";
-import { H1 } from "@usesend/ui";
import { useUrlState } from "~/hooks/useUrlState";
import { ReputationMetrics } from "./reputation-metrics";

25-25: Remove stray leading space in Tailwind class.

Minor formatting nit likely caught by Prettier with the Tailwind plugin.

- <div className=" space-y-12">+ <div className="space-y-12">
apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx (2)

3-16: Group and alphabetize imports; keep external libs together.

Put @usesend/ui imports together, then next/link and react, then ~/ local.

-import {- Breadcrumb,- BreadcrumbItem,- BreadcrumbLink,- BreadcrumbList,- BreadcrumbPage,- BreadcrumbSeparator,-} from "@usesend/ui/src/breadcrumb";-import Link from "next/link";-import { H2 } from "@usesend/ui";--import Spinner from "@usesend/ui/src/spinner";-import { api } from "~/trpc/react";-import { use } from "react";+import { H2 } from "@usesend/ui";+import {+ Breadcrumb,+ BreadcrumbItem,+ BreadcrumbLink,+ BreadcrumbList,+ BreadcrumbPage,+ BreadcrumbSeparator,+} from "@usesend/ui/src/breadcrumb";+import Spinner from "@usesend/ui/src/spinner";+import Link from "next/link";+import { use } from "react";+import { api } from "~/trpc/react";

41-62: Guard against divide-by-zero when computing percentages.

If delivered is 0, percentages become Infinity/NaN.

- const statusCards = [+ const percentOf = (n: number, d: number) => (d > 0 ? (n / d) * 100 : 0);+ const statusCards = [
{
status: "delivered",
count: campaign.delivered,
percentage: 100,
},
{
status: "unsubscribed",
count: campaign.unsubscribed,
- percentage: (campaign.unsubscribed / campaign.delivered) * 100,+ percentage: percentOf(campaign.unsubscribed, campaign.delivered),
},
{
status: "clicked",
count: campaign.clicked,
- percentage: (campaign.clicked / campaign.delivered) * 100,+ percentage: percentOf(campaign.clicked, campaign.delivered),
},
{
status: "opened",
count: campaign.opened,
- percentage: (campaign.opened / campaign.delivered) * 100,+ percentage: percentOf(campaign.opened, campaign.delivered),
},
];
apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx (5)

3-31: Reorder and group imports per guidelines.

External (@usesend/ui, next/link, react) first, then local (~/, ../, ./); alphabetize within groups.

-import { api } from "~/trpc/react";-import { Domain, DomainStatus } from "@prisma/client";+import { Domain, DomainStatus } from "@prisma/client";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@usesend/ui/src/breadcrumb";
-import { DomainStatusBadge } from "../domain-badge";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@usesend/ui/src/table";
import { TextWithCopyButton } from "@usesend/ui/src/text-with-copy";
-import React, { use } from "react";
import { Switch } from "@usesend/ui/src/switch";
-import DeleteDomain from "./delete-domain";-import SendTestMail from "./send-test-mail";
import { Button } from "@usesend/ui/src/button";
-import Link from "next/link";
import { toast } from "@usesend/ui/src/toaster";
import { H1 } from "@usesend/ui";
+import Link from "next/link";+import React, { use } from "react";+import { api } from "~/trpc/react";+import { DomainStatusBadge } from "../domain-badge";+import DeleteDomain from "./delete-domain";+import SendTestMail from "./send-test-mail";

30-31: H1 import is unused; header markup is commented out. Decide: enable H1 or remove import.

Given the rebrand, showing the domain as an H1 seems intended.

Option A — enable H1:

- <div className="flex items-center gap-4">- {/* <div className="flex items-center gap-4">- <H1>{domainQuery.data?.name}</H1>- </div> */}+ <div className="flex items-center gap-4">+ <H1>{domainQuery.data?.name}</H1>+ </div>

Option B — keep as-is; remove the unused H1 import:

-import { H1 } from "@usesend/ui";

Also applies to: 70-73


240-251: Optimistic toggle without error handling can desync UI.

If the mutation fails, the switch stays toggled incorrectly.

 function handleClickTrackingChange() {
- setClickTracking(!clickTracking);- updateDomain.mutate(+ const next = !clickTracking;+ setClickTracking(next);+ updateDomain.mutate(- { id: domain.id, clickTracking: !clickTracking },+ { id: domain.id, clickTracking: next },
{
onSuccess: () => {
utils.domain.invalidate();
toast.success("Click tracking updated");
},
+ onError: (e) => {+ setClickTracking(!next);+ toast.error(e.message ?? "Failed to update click tracking");+ },
},
);
}

253-264: Mirror the same error handling for open tracking.

 function handleOpenTrackingChange() {
- setOpenTracking(!openTracking);- updateDomain.mutate(- { id: domain.id, openTracking: !openTracking },+ const next = !openTracking;+ setOpenTracking(next);+ updateDomain.mutate(+ { id: domain.id, openTracking: next },
{
onSuccess: () => {
utils.domain.invalidate();
toast.success("Open tracking updated");
},
+ onError: (e) => {+ setOpenTracking(!next);+ toast.error(e.message ?? "Failed to update open tracking");+ },
},
);
}

306-331: Tighten the prop type for DnsVerificationStatus.

Use DomainStatus instead of string for better type safety.

-const DnsVerificationStatus: React.FC<{ status: string }> = ({ status }) => {+const DnsVerificationStatus: React.FC<{ status: DomainStatus | string }> = ({+ status,+}) => {

If all call sites pass DomainStatus, consider dropping the string union.

packages/ui/src/typography.tsx (4)

7-9: asChild is declared but unused. Implement or remove.

If you want Slot-based composition, wire it up; else drop the prop to avoid confusion.

Implement via Radix Slot:

+import { Slot } from "@radix-ui/react-slot";
export interface TypographyProps extends React.HTMLAttributes<HTMLElement> {
asChild?: boolean;
}

Example for H1:

-export const H1 = React.forwardRef<HTMLHeadingElement, TypographyProps>(- ({ className, ...props }, ref) => (- <h1+export const H1 = React.forwardRef<HTMLHeadingElement, TypographyProps>(+ ({ asChild, className, ...props }, ref) => {+ const Comp: any = asChild ? Slot : "h1";+ return (+ <Comp
ref={ref}
className={cn(
// font-mono, larger and a bit bolder
- " font-mono text-xl font-medium",+ "font-mono text-xl font-medium",
className
)}
{...props}
- />- )+ />+ );+ }
);

Repeat similarly for H2/BodyText with the correct underlying tag.


17-17: Remove the leading space in the class string.

- " font-mono text-xl font-medium",+ "font-mono text-xl font-medium",

31-33: Comment says “bold” but class lacks a weight. Align code or comment.

- // font-mono, slightly smaller than H1, bold- "font-mono text-lg",+ // font-mono, slightly smaller than H1+ "font-mono text-lg font-medium",

11-23: Stronger typing per element improves DX.

Use element-specific props for better IntelliSense and attribute checks.

-export const H1 = React.forwardRef<HTMLHeadingElement, TypographyProps>(+export const H1 = React.forwardRef<+ HTMLHeadingElement,+ React.ComponentPropsWithoutRef<"h1">+>(
...
-export const H2 = React.forwardRef<HTMLHeadingElement, TypographyProps>(+export const H2 = React.forwardRef<+ HTMLHeadingElement,+ React.ComponentPropsWithoutRef<"h2">+>(
...
-export const BodyText = React.forwardRef<HTMLParagraphElement, TypographyProps>(+export const BodyText = React.forwardRef<+ HTMLParagraphElement,+ React.ComponentPropsWithoutRef<"p">+>(

If keeping asChild, keep the shared TypographyProps and intersect it with the element props.

Also applies to: 26-38, 41-53

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7f45355 and 9840677.

📒 Files selected for processing (17)
  • apps/web/src/app/(dashboard)/admin/page.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx (3 hunks)
  • apps/web/src/app/(dashboard)/campaigns/page.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/contacts/page.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/dashboard/page.tsx (2 hunks)
  • apps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/dev-settings/page.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx (9 hunks)
  • apps/web/src/app/(dashboard)/domains/page.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/emails/page.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/payments/page.tsx (2 hunks)
  • apps/web/src/app/(dashboard)/suppressions/page.tsx (2 hunks)
  • apps/web/src/app/(dashboard)/templates/page.tsx (1 hunks)
  • apps/web/src/components/settings/AddSesSettings.tsx (6 hunks)
  • packages/ui/index.ts (1 hunks)
  • packages/ui/src/sheet.tsx (1 hunks)
  • packages/ui/src/typography.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/app/(dashboard)/payments/page.tsx
  • apps/web/src/app/(dashboard)/suppressions/page.tsx
🧰 Additional context used
📓 Path-based instructions (8)
{apps,packages}/**/*.{js,jsx,ts,tsx,css,scss,md,mdx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier with the Tailwind plugin for code formatting

Files:

  • apps/web/src/app/(dashboard)/dev-settings/page.tsx
  • apps/web/src/app/(dashboard)/contacts/page.tsx
  • packages/ui/index.ts
  • apps/web/src/app/(dashboard)/campaigns/page.tsx
  • apps/web/src/app/(dashboard)/dashboard/page.tsx
  • apps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsx
  • apps/web/src/app/(dashboard)/emails/page.tsx
  • apps/web/src/app/(dashboard)/admin/page.tsx
  • apps/web/src/app/(dashboard)/domains/page.tsx
  • packages/ui/src/typography.tsx
  • apps/web/src/app/(dashboard)/templates/page.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx
  • packages/ui/src/sheet.tsx
{apps,packages}/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{js,jsx,ts,tsx}: Group imports by source (internal/external) and alphabetize them
Use camelCase for variables and functions, PascalCase for components and classes
Use try/catch with specific error types for error handling

Files:

  • apps/web/src/app/(dashboard)/dev-settings/page.tsx
  • apps/web/src/app/(dashboard)/contacts/page.tsx
  • packages/ui/index.ts
  • apps/web/src/app/(dashboard)/campaigns/page.tsx
  • apps/web/src/app/(dashboard)/dashboard/page.tsx
  • apps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsx
  • apps/web/src/app/(dashboard)/emails/page.tsx
  • apps/web/src/app/(dashboard)/admin/page.tsx
  • apps/web/src/app/(dashboard)/domains/page.tsx
  • packages/ui/src/typography.tsx
  • apps/web/src/app/(dashboard)/templates/page.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx
  • packages/ui/src/sheet.tsx
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{ts,tsx}: Use strong typing in TypeScript, avoid any, and use Zod for validation
Follow Vercel style guides with strict TypeScript

Files:

  • apps/web/src/app/(dashboard)/dev-settings/page.tsx
  • apps/web/src/app/(dashboard)/contacts/page.tsx
  • packages/ui/index.ts
  • apps/web/src/app/(dashboard)/campaigns/page.tsx
  • apps/web/src/app/(dashboard)/dashboard/page.tsx
  • apps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsx
  • apps/web/src/app/(dashboard)/emails/page.tsx
  • apps/web/src/app/(dashboard)/admin/page.tsx
  • apps/web/src/app/(dashboard)/domains/page.tsx
  • packages/ui/src/typography.tsx
  • apps/web/src/app/(dashboard)/templates/page.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx
  • packages/ui/src/sheet.tsx
{apps,packages}/**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{jsx,tsx}: Use functional React components with hooks and group related hooks together
In React components, structure code with props at the top, hooks next, helper functions, then JSX

Files:

  • apps/web/src/app/(dashboard)/dev-settings/page.tsx
  • apps/web/src/app/(dashboard)/contacts/page.tsx
  • apps/web/src/app/(dashboard)/campaigns/page.tsx
  • apps/web/src/app/(dashboard)/dashboard/page.tsx
  • apps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsx
  • apps/web/src/app/(dashboard)/emails/page.tsx
  • apps/web/src/app/(dashboard)/admin/page.tsx
  • apps/web/src/app/(dashboard)/domains/page.tsx
  • packages/ui/src/typography.tsx
  • apps/web/src/app/(dashboard)/templates/page.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx
  • packages/ui/src/sheet.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use tRPC for internal API endpoints

In the web app, use the / alias for src imports (e.g., import { x } from "/utils/x")

Files:

  • apps/web/src/app/(dashboard)/dev-settings/page.tsx
  • apps/web/src/app/(dashboard)/contacts/page.tsx
  • apps/web/src/app/(dashboard)/campaigns/page.tsx
  • apps/web/src/app/(dashboard)/dashboard/page.tsx
  • apps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsx
  • apps/web/src/app/(dashboard)/emails/page.tsx
  • apps/web/src/app/(dashboard)/admin/page.tsx
  • apps/web/src/app/(dashboard)/domains/page.tsx
  • apps/web/src/app/(dashboard)/templates/page.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Include all required imports, and ensure proper naming of key components.

Files:

  • apps/web/src/app/(dashboard)/dev-settings/page.tsx
  • apps/web/src/app/(dashboard)/contacts/page.tsx
  • packages/ui/index.ts
  • apps/web/src/app/(dashboard)/campaigns/page.tsx
  • apps/web/src/app/(dashboard)/dashboard/page.tsx
  • apps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsx
  • apps/web/src/app/(dashboard)/emails/page.tsx
  • apps/web/src/app/(dashboard)/admin/page.tsx
  • apps/web/src/app/(dashboard)/domains/page.tsx
  • packages/ui/src/typography.tsx
  • apps/web/src/app/(dashboard)/templates/page.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx
  • packages/ui/src/sheet.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation and semicolons (enforced by Prettier) in TypeScript files

Files:

  • apps/web/src/app/(dashboard)/dev-settings/page.tsx
  • apps/web/src/app/(dashboard)/contacts/page.tsx
  • packages/ui/index.ts
  • apps/web/src/app/(dashboard)/campaigns/page.tsx
  • apps/web/src/app/(dashboard)/dashboard/page.tsx
  • apps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsx
  • apps/web/src/app/(dashboard)/emails/page.tsx
  • apps/web/src/app/(dashboard)/admin/page.tsx
  • apps/web/src/app/(dashboard)/domains/page.tsx
  • packages/ui/src/typography.tsx
  • apps/web/src/app/(dashboard)/templates/page.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx
  • packages/ui/src/sheet.tsx
{apps/web,apps/marketing,packages/ui,packages/email-editor}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Name React component files in PascalCase (e.g., AppSideBar.tsx)

Files:

  • apps/web/src/app/(dashboard)/dev-settings/page.tsx
  • apps/web/src/app/(dashboard)/contacts/page.tsx
  • apps/web/src/app/(dashboard)/campaigns/page.tsx
  • apps/web/src/app/(dashboard)/dashboard/page.tsx
  • apps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsx
  • apps/web/src/app/(dashboard)/emails/page.tsx
  • apps/web/src/app/(dashboard)/admin/page.tsx
  • apps/web/src/app/(dashboard)/domains/page.tsx
  • packages/ui/src/typography.tsx
  • apps/web/src/app/(dashboard)/templates/page.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx
  • packages/ui/src/sheet.tsx
🧠 Learnings (2)
📚 Learning: 2025-08-31T11:01:50.038Z
Learnt from: CR
PR: unsend-dev/unsend#0
File: AGENTS.md:0-0
Timestamp: 2025-08-31T11:01:50.038Z
Learning: Applies to {apps/web,apps/marketing,packages/ui,packages/email-editor}/**/*.tsx : Name React component files in PascalCase (e.g., AppSideBar.tsx)

Applied to files:

  • apps/web/src/app/(dashboard)/emails/page.tsx
  • apps/web/src/app/(dashboard)/templates/page.tsx
📚 Learning: 2025-08-09T10:37:58.146Z
Learnt from: CR
PR: unsend-dev/unsend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-09T10:37:58.146Z
Learning: Applies to {apps,packages}/**/*.{jsx,tsx} : Use functional React components with hooks and group related hooks together

Applied to files:

  • packages/ui/src/typography.tsx
🧬 Code graph analysis (11)
apps/web/src/app/(dashboard)/dev-settings/page.tsx (3)
apps/web/src/app/(dashboard)/admin/page.tsx (1)
  • ApiKeysPage (7-20)
apps/web/src/app/(dashboard)/dev-settings/layout.tsx (1)
  • ApiKeysPage (7-22)
packages/ui/src/typography.tsx (1)
  • H1 (11-23)
apps/web/src/app/(dashboard)/contacts/page.tsx (2)
apps/web/src/app/(dashboard)/campaigns/page.tsx (1)
  • ContactsPage (7-17)
packages/ui/src/typography.tsx (1)
  • H1 (11-23)
apps/web/src/app/(dashboard)/campaigns/page.tsx (3)
apps/web/src/app/(dashboard)/contacts/page.tsx (1)
  • ContactsPage (7-17)
packages/ui/index.ts (1)
  • H1 (4-4)
packages/ui/src/typography.tsx (1)
  • H1 (11-23)
apps/web/src/app/(dashboard)/dashboard/page.tsx (2)
packages/ui/index.ts (1)
  • H1 (4-4)
packages/ui/src/typography.tsx (1)
  • H1 (11-23)
apps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsx (2)
packages/ui/index.ts (1)
  • H1 (4-4)
packages/ui/src/typography.tsx (1)
  • H1 (11-23)
apps/web/src/app/(dashboard)/emails/page.tsx (2)
packages/ui/index.ts (1)
  • H1 (4-4)
packages/ui/src/typography.tsx (1)
  • H1 (11-23)
apps/web/src/app/(dashboard)/admin/page.tsx (3)
apps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsx (1)
  • ApiKeysPage (7-17)
packages/ui/index.ts (1)
  • H1 (4-4)
packages/ui/src/typography.tsx (1)
  • H1 (11-23)
apps/web/src/app/(dashboard)/domains/page.tsx (2)
packages/ui/index.ts (1)
  • H1 (4-4)
packages/ui/src/typography.tsx (1)
  • H1 (11-23)
packages/ui/src/typography.tsx (1)
packages/ui/index.ts (4)
  • H1 (4-4)
  • cn (3-3)
  • H2 (4-4)
  • BodyText (4-4)
apps/web/src/app/(dashboard)/templates/page.tsx (2)
packages/ui/index.ts (1)
  • H1 (4-4)
packages/ui/src/typography.tsx (1)
  • H1 (11-23)
apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx (2)
packages/ui/index.ts (1)
  • H2 (4-4)
packages/ui/src/typography.tsx (1)
  • H2 (26-38)
🔇 Additional comments (15)
apps/web/src/components/settings/AddSesSettings.tsx (1)

24-24: Rename to usesendUrl is consistent across schema, defaults, field, and errors. LGTM.

Also applies to: 59-59, 146-147, 157-157

apps/web/src/app/(dashboard)/domains/page.tsx (1)

11-11: Heading refactor to H1 looks good.

Consistent with the new typography system.

apps/web/src/app/(dashboard)/admin/page.tsx (1)

11-11: Heading refactor to H1 looks good.

Matches the rebrand typography.

apps/web/src/app/(dashboard)/contacts/page.tsx (1)

11-11: Heading refactor to H1 looks good.

Consistent with other dashboard pages.

apps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsx (1)

11-11: Heading refactor to H1 looks good.

Typography aligned with the design system.

apps/web/src/app/(dashboard)/dev-settings/page.tsx (1)

11-11: Heading migration looks good.

Consistent with the new typography primitives.

apps/web/src/app/(dashboard)/emails/page.tsx (1)

10-10: LGTM on H1 adoption.

apps/web/src/app/(dashboard)/templates/page.tsx (1)

11-11: Typography swap to H1 looks good.

apps/web/src/app/(dashboard)/campaigns/page.tsx (1)

11-11: H1 usage is consistent with the design system.

packages/ui/index.ts (1)

4-4: Public re-exports look good; confirm all symbols exist.

Verify H2 and BodyText are implemented in src/typography.tsx and included in builds.

apps/web/src/app/(dashboard)/dashboard/page.tsx (1)

17-17: Adopting the design-system H1 looks good.

Consistent with the rebrand and shared typography.

apps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsx (2)

84-84: Heading migration to H2 is consistent and clear.

Nice alignment with shared typography.

Also applies to: 114-114


18-24: **Incorrect context: this is a Server Component and use() is only valid in Server Components (Next.js 15 + React 19) (medium.com); no changes required.

Likely an incorrect or invalid review comment.

apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx (1)

44-47: Smart background refetch interval.

Conditionally polling while verifying is a nice touch.

packages/ui/src/typography.tsx (1)

11-55: Good addition of shared Typography primitives.

Forward refs, cn usage, and exports look solid.

Comment on lines +155 to 156
value={`${domainQuery.data?.dkimSelector ?? "unsend"}._domainkey.${domainQuery.data?.subdomain || domainQuery.data?.name}`}
/>

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.

⚠️ Potential issue

DKIM selector fallback should use “usesend”, not “unsend”.

Mismatch with the migration defaults can give users wrong DNS values.

- value={`${domainQuery.data?.dkimSelector ?? "unsend"}._domainkey.${domainQuery.data?.subdomain || domainQuery.data?.name}`}+ value={`${domainQuery.data?.dkimSelector ?? "usesend"}._domainkey.${domainQuery.data?.subdomain || domainQuery.data?.name}`}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
value={`${domainQuery.data?.dkimSelector??"unsend"}._domainkey.${domainQuery.data?.subdomain||domainQuery.data?.name}`}
/>
value={`${domainQuery.data?.dkimSelector??"usesend"}._domainkey.${domainQuery.data?.subdomain||domainQuery.data?.name}`}
/>
🤖 Prompt for AI Agents
In apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx around lines 155 to
156, the DKIM selector fallback string is wrong — change the fallback from
"unsend" to "usesend" so the value becomes `${domainQuery.data?.dkimSelector ??
"usesend"}._domainkey.${domainQuery.data?.subdomain || domainQuery.data?.name}`;
update the literal only (no logic changes) to match the migration defaults and
ensure displayed DNS values are correct.

Comment on lines 25 to 26
sendRate: z.coerce.number(),
transactionalQuota: z.coerce.number().min(0).max(100),

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.

🛠️ Refactor suggestion

Prevent zero/invalid rates; enforce numeric constraints in schema and inputs.

A sendRate of 0 will effectively halt sending. Enforce minimums/ints and reflect them in inputs for better UX.

- sendRate: z.coerce.number(),- transactionalQuota: z.coerce.number().min(0).max(100),+ sendRate: z.coerce.number().int().min(1, "Send rate must be at least 1"),+ transactionalQuota: z.coerce.number().int().min(0).max(100),
- <Input placeholder="1" className="w-full" {...field} />+ <Input type="number" min={1} step={1} placeholder="1" className="w-full" {...field} />
- <Input placeholder="0" className="w-full" {...field} />+ <Input type="number" min={0} max={100} step={1} placeholder="0" className="w-full" {...field} />

Also applies to: 171-176, 190-195

🤖 Prompt for AI Agents
In apps/web/src/components/settings/AddSesSettings.tsx around lines 25-26 (and
also update occurrences at 171-176 and 190-195), the schema currently allows
sendRate (and transactionalQuota) values that permit zero or non-integer
numbers; change the zod coercions to enforce integers and sensible minimums
(e.g., sendRate: z.coerce.number().int().min(1) and transactionalQuota:
z.coerce.number().int().min(0).max(100)), and update the corresponding input
components to use integer-compatible controls (step=1), client-side min
attributes, and validation/error messaging so users cannot submit 0 or
non-integer rates.

ghost 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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/marketing/package.json (1)

13-18: Deps look aligned; confirm no lingering @unsend imports.
Switched to @usesend/* and React 19/Next 15. Ensure source files no longer import @unsend/*.

#!/bin/bash
rg -nP -g 'apps/marketing/**/*.{ts,tsx,js,jsx}' -e '@unsend/' -e "from\s+['\"]unsend['\"]"
🧹 Nitpick comments (14)
apps/marketing/package.json (3)

10-10: Speed up linting.
Enable cache to cut CI time; preserves the strict no-warnings policy.

- "lint": "eslint . --max-warnings 0"+ "lint": "eslint . --max-warnings 0 --cache"

13-15: Consider unpinning React/DOM to caret for workspace dedupe.
Exact pins can cause duplicate installs if other packages use ^19.1.x.

- "react": "19.1.0",- "react-dom": "19.1.0",+ "react": "^19.1.0",+ "react-dom": "^19.1.0",

20-31: Remove redundant @next/eslint-plugin-next
.eslintrc.cjs in apps/marketing extends @usesend/eslint-config/next.js (which already includes the Next.js ESLint plugin); drop it from apps/marketing/package.json:

- "@next/eslint-plugin-next": "^15.3.1",

Ensure @typescript-eslint/parser and @typescript-eslint/eslint-plugin remain on major v8 (currently 8.31.0).

apps/marketing/next.config.js (2)

11-11: Confirm module system; export default in .js requires ESM.

If apps/marketing/package.json (or repo root) doesn’t set "type": "module", Next will treat next.config.js as CJS and this export can break config loading. Either keep ESM with .mjs/ESM package or switch to CJS export.

Apply this if staying on .js (CJS):

-export default config;+module.exports = config;

1-2: Optional: Use next.config.ts to align with repo TypeScript preference.

Keeps types first-class and follows the guideline to prefer TS for new files.

Example:

// apps/marketing/next.config.tsimporttype{NextConfig}from"next";constconfig: NextConfig={output: "export",images: {unoptimized: true},};exportdefaultconfig;
apps/marketing/tailwind.config.ts (2)

1-3: Nit: alphabetize imports per repo guidelines.

Group/alpha-order external imports (e.g., path, tailwind-config, tailwindcss). No functional impact.

-import { type Config } from "tailwindcss";-import sharedConfig from "@usesend/tailwind-config/tailwind.config";-import path from "path";+import path from "path";+import sharedConfig from "@usesend/tailwind-config/tailwind.config";+import { type Config } from "tailwindcss";

7-11: Optional: include md/mdx in content if used by marketing.

If components/classes appear in MD/MDX, add them to avoid purge.

 content: [
- "./src/**/*.tsx",+ "./src/**/*.{ts,tsx,md,mdx}",
apps/marketing/src/app/layout.tsx (3)

3-7: Group and alphabetize imports; merge duplicate next/font imports.
Keeps imports tidy per repo guidelines while preserving the CSS side-effect import at the top.

Apply:

-import { Inter } from "next/font/google";-import { JetBrains_Mono } from "next/font/google";-import type { Metadata } from "next";-import { ThemeProvider } from "@usesend/ui";+import { ThemeProvider } from "@usesend/ui";+import type { Metadata } from "next";+import { Inter, JetBrains_Mono } from "next/font/google";

24-28: Optional: extract Props type for clarity.
Minor readability/consistency with strict TS styles.

-export default function RootLayout({- children,-}: {- children: React.ReactNode;-}) {+type RootLayoutProps = { children: React.ReactNode };+export default function RootLayout({ children }: RootLayoutProps) {

30-36: Verify theme/bg choices; consider removing duplicate bg class.

  • Ensure bg-sidebar-background exists in marketing theme (not only web).
  • You set bg-sidebar-background on both html and body; one is sufficient unless you need both. Consider leaving it on body only.
-<html lang="en" suppressHydrationWarning className="bg-sidebar-background">+<html lang="en" suppressHydrationWarning>

Also confirm Toaster removal is intentional for marketing (no toast usage).

apps/marketing/src/components/GitHubStarsButton.tsx (4)

4-4: Import from the package’s public entrypoint, not internal /src.

Prevents fragile coupling to internal structure and improves treeshaking.

-import { Button } from "@usesend/ui/src/button";+import { Button } from "@usesend/ui/button";

17-26: Validate and type the GitHub response (strict TS + Zod).

Avoids any from res.json() and enforces schema per guidelines.

+import { z } from "zod";
@@
- const json = await res.json();- if (!cancelled && typeof json.stargazers_count === "number") {- setStars(json.stargazers_count);- }+ const RepoSchema = z.object({ stargazers_count: z.number() });+ const parsed = RepoSchema.safeParse(await res.json());+ if (!cancelled && parsed.success) {+ setStars(parsed.data.stargazers_count);+ }

14-16: Prefer AbortController over a boolean flag for fetch cancellation.

Prevents unnecessary work and avoids setState after unmount.

- let cancelled = false;+ let cancelled = false;+ const controller = new AbortController();
@@
- const res = await fetch(API_URL, {+ const res = await fetch(API_URL, {
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
cache: "no-store",
+ signal: controller.signal,
});
@@
- return () => {- cancelled = true;- };+ return () => {+ cancelled = true;+ controller.abort();+ };

Also applies to: 22-23, 34-36


6-9: Consider server-side caching to avoid client rate limits.

Hitting the GitHub API from every client risks rate limiting. Expose a tiny server route (revalidate ~60s) that your client calls.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9840677 and 128031f.

⛔ Files ignored due to path filters (3)
  • apps/marketing/public/favicon.ico is excluded by !**/*.ico
  • apps/marketing/public/logo-squircle.png is excluded by !**/*.png
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • apps/marketing/.eslintrc.cjs (1 hunks)
  • apps/marketing/next-env.d.ts (1 hunks)
  • apps/marketing/next.config.js (1 hunks)
  • apps/marketing/package.json (1 hunks)
  • apps/marketing/postcss.config.cjs (1 hunks)
  • apps/marketing/src/app/layout.tsx (1 hunks)
  • apps/marketing/src/app/page.tsx (1 hunks)
  • apps/marketing/src/components/GitHubStarsButton.tsx (1 hunks)
  • apps/marketing/tailwind.config.ts (1 hunks)
  • apps/marketing/tsconfig.json (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/marketing/next-env.d.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/marketing/.eslintrc.cjs
  • apps/marketing/tsconfig.json
🧰 Additional context used
📓 Path-based instructions (8)
{apps,packages}/**/*.{js,jsx,ts,tsx,css,scss,md,mdx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier with the Tailwind plugin for code formatting

Files:

  • apps/marketing/next.config.js
  • apps/marketing/src/components/GitHubStarsButton.tsx
  • apps/marketing/tailwind.config.ts
  • apps/marketing/src/app/page.tsx
  • apps/marketing/src/app/layout.tsx
{apps,packages}/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{js,jsx,ts,tsx}: Group imports by source (internal/external) and alphabetize them
Use camelCase for variables and functions, PascalCase for components and classes
Use try/catch with specific error types for error handling

Files:

  • apps/marketing/next.config.js
  • apps/marketing/src/components/GitHubStarsButton.tsx
  • apps/marketing/tailwind.config.ts
  • apps/marketing/src/app/page.tsx
  • apps/marketing/src/app/layout.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Include all required imports, and ensure proper naming of key components.

Files:

  • apps/marketing/next.config.js
  • apps/marketing/src/components/GitHubStarsButton.tsx
  • apps/marketing/tailwind.config.ts
  • apps/marketing/src/app/page.tsx
  • apps/marketing/src/app/layout.tsx
**/*.{js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer TypeScript over JavaScript; new source files should use .ts/.tsx instead of .js/.jsx

Files:

  • apps/marketing/next.config.js
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{ts,tsx}: Use strong typing in TypeScript, avoid any, and use Zod for validation
Follow Vercel style guides with strict TypeScript

Files:

  • apps/marketing/src/components/GitHubStarsButton.tsx
  • apps/marketing/tailwind.config.ts
  • apps/marketing/src/app/page.tsx
  • apps/marketing/src/app/layout.tsx
{apps,packages}/**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{jsx,tsx}: Use functional React components with hooks and group related hooks together
In React components, structure code with props at the top, hooks next, helper functions, then JSX

Files:

  • apps/marketing/src/components/GitHubStarsButton.tsx
  • apps/marketing/src/app/page.tsx
  • apps/marketing/src/app/layout.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation and semicolons (enforced by Prettier) in TypeScript files

Files:

  • apps/marketing/src/components/GitHubStarsButton.tsx
  • apps/marketing/tailwind.config.ts
  • apps/marketing/src/app/page.tsx
  • apps/marketing/src/app/layout.tsx
{apps/web,apps/marketing,packages/ui,packages/email-editor}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Name React component files in PascalCase (e.g., AppSideBar.tsx)

Files:

  • apps/marketing/src/components/GitHubStarsButton.tsx
  • apps/marketing/src/app/page.tsx
  • apps/marketing/src/app/layout.tsx
🧠 Learnings (2)
📚 Learning: 2025-08-09T10:37:58.146Z
Learnt from: CR
PR: unsend-dev/unsend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-09T10:37:58.146Z
Learning: Applies to {apps,packages}/**/*.{js,jsx,ts,tsx,css,scss,md,mdx} : Use Prettier with the Tailwind plugin for code formatting

Applied to files:

  • apps/marketing/postcss.config.cjs
  • apps/marketing/tailwind.config.ts
📚 Learning: 2025-08-09T10:38:11.831Z
Learnt from: CR
PR: unsend-dev/unsend#0
File: .windsurfrules:0-0
Timestamp: 2025-08-09T10:38:11.831Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Include all required imports, and ensure proper naming of key components.

Applied to files:

  • apps/marketing/tailwind.config.ts
🧬 Code graph analysis (4)
apps/marketing/next.config.js (1)
apps/marketing/postcss.config.cjs (1)
  • config (1-5)
apps/marketing/postcss.config.cjs (1)
apps/marketing/next.config.js (1)
  • config (2-9)
apps/marketing/src/app/page.tsx (1)
apps/marketing/src/components/GitHubStarsButton.tsx (1)
  • GitHubStarsButton (10-55)
apps/marketing/src/app/layout.tsx (2)
apps/web/src/app/layout.tsx (2)
  • metadata (21-25)
  • RootLayout (27-44)
packages/ui/index.ts (1)
  • ThemeProvider (5-5)
🔇 Additional comments (13)
apps/marketing/package.json (2)

5-5: ESM flip verified: configs use .cjs extension Both ESLint (.eslintrc.cjs) and PostCSS (postcss.config.cjs) configs in apps/marketing are explicitly .cjs, so they’ll load correctly under "type": "module".


32-36: Version alignment verified: postcss ^8.5.3, prettier ^3.5.3, and typescript ^5.8.3 exist on npm and match all other workspaces.

apps/marketing/next.config.js (1)

4-8: Approved: static export config validated
No server actions, middleware, API routes, Next/Headers imports, or dynamic rendering flags detected in apps/marketing.

apps/marketing/tailwind.config.ts (1)

2-2: Rebrand import path LGTM.

Import now points to @usesend; aligned with PR objective.

apps/marketing/postcss.config.cjs (1)

1-7: Confirm autoprefixer removal; re-add if not intentional.

With a custom PostCSS config, Next.js won’t auto-inject autoprefixer. If you still need vendor prefixes, add it back.

 const config = {
plugins: {
- tailwindcss: {},+ tailwindcss: {},+ autoprefixer: {},
},
};
apps/marketing/src/app/layout.tsx (4)

1-1: Rebrand CSS path looks correct.
Import path switched to @usesend scope as expected.


8-11: Inter font config: LGTM.
Subsets + CSS var are correct.


13-16: JetBrains Mono font config: LGTM.
Variables align with tailwind font-mono mapping.


19-20: Canonical brand casing is “useSend”; fix typo in web layout description.

  • No changes needed for brand casing (all instances correctly use “useSend”).
  • apps/web/src/app/layout.tsx:23: change description: "Open source email platoform" to description: "Open source email platform" [apps/web/src/app/layout.tsx:23]
⛔ Skipped due to learnings
Learnt from: CR
PR: unsend-dev/unsend#0
File: AGENTS.md:0-0
Timestamp: 2025-08-31T11:01:50.038Z
Learning: Applies to {apps/web,apps/marketing,packages/ui,packages/email-editor}/**/*.tsx : Name React component files in PascalCase (e.g., AppSideBar.tsx)
apps/marketing/src/app/page.tsx (4)

17-17: Verify Tailwind token text-blue exists (likely needs a shade).

If not a custom color, switch to a standard shade.

- <h1 className="text-xl font-mono font-medium text-blue">useSend</h1>+ <h1 className="text-xl font-mono font-medium text-blue-600">useSend</h1>

4-26: LGTM on the minimal landing page refactor.

Clean, focused hero; Server component importing a client CTA is fine.


10-11: Asset confirmedapps/marketing/public/logo-squircle.png exists; no 404 risk.


2-2: No action needed: ~/* alias is configured. Verified in apps/marketing/tsconfig.json and apps/web/tsconfig.json that ~/* maps to ./src/*.

Comment on lines +42 to +53
<Button variant="outline" size="lg" className="px-4 gap-2">
<a
href={REPO_URL}
target="_blank"
rel="noopener noreferrer"
aria-label="Star this repo on GitHub"
className="flex items-center gap-2"
>
<GitHubIcon className="h-4 w-4" />
<span>Star on GitHub</span>
</a>
</Button>

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.

⚠️ Potential issue

Fix invalid nested interactive elements (anchor inside Button).

This renders a inside a , which is invalid HTML/accessibility and can break clicks/keyboard. Render the anchor as the Button via asChild (or style the anchor directly).

Apply:

- return (- <Button variant="outline" size="lg" className="px-4 gap-2">- <a+ return (+ <Button asChild variant="outline" size="lg" className="px-4 gap-2">+ <a
href={REPO_URL}
target="_blank"
rel="noopener noreferrer"
aria-label="Star this repo on GitHub"
className="flex items-center gap-2"
>
<GitHubIcon className="h-4 w-4" />
- <span>Star on GitHub</span>+ <span>Star on GitHub • {formatted}</span>
</a>
</Button>
);

If Button doesn’t support asChild, drop Button and style the <a> with the same classes.

📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Buttonvariant="outline"size="lg"className="px-4 gap-2">
<a
href={REPO_URL}
target="_blank"
rel="noopener noreferrer"
aria-label="Star this repo on GitHub"
className="flex items-center gap-2"
>
<GitHubIconclassName="h-4 w-4"/>
<span>Star on GitHub</span>
</a>
</Button>
return(
<ButtonasChildvariant="outline"size="lg"className="px-4 gap-2">
<a
href={REPO_URL}
target="_blank"
rel="noopener noreferrer"
aria-label="Star this repo on GitHub"
className="flex items-center gap-2"
>
<GitHubIconclassName="h-4 w-4"/>
<span>Star on GitHub • {formatted}</span>
</a>
</Button>
);
🤖 Prompt for AI Agents
In apps/marketing/src/components/GitHubStarsButton.tsx around lines 42-53 there
is an invalid nested interactive element (an <a> inside the Button), which
breaks HTML semantics and accessibility; change the markup so the anchor is the
actual button: if the Button component supports asChild, render Button asChild
and pass the anchor as its child (keeping href, target, rel, aria-label,
className, GitHubIcon and span inside the anchor); if Button does not support
asChild, remove the Button and apply the Button's classes/props directly to the
<a> element so the anchor is styled/behaves like the button while preserving
accessibility attributes.

Comment on lines +9 to +10
`${path.join(require.resolve("@usesend/ui"), "..")}/**/*.{ts,tsx}`,
`${path.join(require.resolve("@usesend/email-editor"), "..")}/**/*.{ts,tsx}`,

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.

🛠️ Refactor suggestion

Make Tailwind content globs robust (pkg root + JS extensions) and ESM-safe.

  • require is undefined in ESM .ts configs; use createRequire.
  • Resolving the package entry then joining ".." can land in dist and miss sources. Resolve package.json and use dirname.
  • Include js/jsx/mjs/cjs to ensure classes from built packages are picked up.

Apply this diff to adjust the globs:

- `${path.join(require.resolve("@usesend/ui"), "..")}/**/*.{ts,tsx}`,- `${path.join(require.resolve("@usesend/email-editor"), "..")}/**/*.{ts,tsx}`,+ ${path.join(path.dirname(require.resolve("@usesend/ui/package.json")), "**/*.{js,jsx,ts,tsx,mjs,cjs}")},+ ${path.join(path.dirname(require.resolve("@usesend/email-editor/package.json")), "**/*.{js,jsx,ts,tsx,mjs,cjs}")},

And add these lines near the top to make require available in ESM:

import{createRequire}from"module";constrequire=createRequire(import.meta.url);
🤖 Prompt for AI Agents
In apps/marketing/tailwind.config.ts around lines 9-10, the current globs use
require (broken in ESM), resolve package main (can point to dist) and only
include ts/tsx; fix by importing createRequire from "module" and creating a
require bound to import.meta.url near the top, use require.resolve to locate
each package's package.json then take its dirname (not ".." from the entry file)
to point at the package root, and extend the glob extensions to include
.js,.jsx,.mjs,.cjs as well as .ts,.tsx so built outputs are picked up; update
the two lines to build globs from dirname(require.resolve("pkg/package.json")) +
'/**/*.{ts,tsx,js,jsx,mjs,cjs}' accordingly.

ghost 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/web/src/components/settings/AddSesSettings.tsx (1)

150-156: Use appropriate input types and constraints.

Improve UX and prevent invalid values at source.

- <Input+ <Input+ type="url"
placeholder="https://example.com"
className="w-full"
{...field}
/>
- <Input placeholder="1" className="w-full" {...field} />+ <Input+ type="number"+ min={1}+ step={1}+ placeholder="1"+ className="w-full"+ {...field}+ />
- <Input placeholder="0" className="w-full" {...field} />+ <Input+ type="number"+ min={0}+ max={100}+ step={1}+ placeholder="0"+ className="w-full"+ {...field}+ />

Also applies to: 171-176, 190-195

apps/web/src/server/api/routers/admin.ts (1)

43-49: Apply the same numeric constraints to update flow.

Prevents drifting constraints between create/update.

 .input(
z.object({
settingsId: z.string(),
- sendRate: z.number(),- transactionalQuota: z.number(),+ sendRate: z.number().int().min(1),+ transactionalQuota: z.number().int().min(0).max(100),
}),
)
apps/web/src/server/service/ses-settings-service.ts (2)

202-211: Reset topicArns to avoid duplicate growth across invalidations.

Currently appends on every call; memory bloat and duplicate ARNs.

 static async invalidateCache() {
- this.cache = {};+ this.cache = {};+ this.topicArns = [];
const settings = await db.sesSetting.findMany();
settings.forEach((setting) => {
this.cache[setting.region] = setting;
if (setting.topicArn) {
this.topicArns.push(setting.topicArn);
}
});
}

276-296: SSRF risk and no timeout when validating usesendUrl. Add protocol/host checks and a fetch timeout.

Prevents internal network hits and hung requests.

-async function isValidUsesendUrl(url: string) {- logger.info({ url }, "Checking if URL is valid");- try {- const response = await fetch(`${url}/api/ses_callback`, {- method: "GET",- });- return {- isValid: response.status === 200,- code: response.status,- error: response.statusText,- };- } catch (e) {- logger.error({ err: e }, "Error checking if URL is valid");- return {- isValid: false,- code: 500,- error: e,- };- }-}+async function isValidUsesendUrl(url: string) {+ logger.info({ url }, "Checking if URL is valid");+ try {+ const u = new URL(url);+ if (u.protocol !== "https:") {+ return { isValid: false, code: 400, error: "URL must use https" };+ }+ const hostname = u.hostname;+ const privateHosts = [+ /^localhost$/i,+ /^127\./,+ /^10\./,+ /^192\.168\./,+ /^172\.(1[6-9]|2[0-9]|3[0-1])\./,+ /^::1$/i,+ ];+ if (privateHosts.some((re) => re.test(hostname))) {+ return { isValid: false, code: 400, error: "URL must be public" };+ }+ const controller = new AbortController();+ const timeout = setTimeout(() => controller.abort(), 5000);+ const target = new URL("/api/ses_callback", u.href).toString();+ const response = await fetch(target, { method: "GET", signal: controller.signal });+ clearTimeout(timeout);+ return {+ isValid: response.ok,+ code: response.status,+ error: response.statusText,+ };+ } catch (e) {+ logger.error({ err: e }, "Error checking if URL is valid");+ return {+ isValid: false,+ code: 500,+ error: e instanceof Error ? e.message : String(e),+ };+ }+}
apps/web/src/app/(dashboard)/domains/add-domain.tsx (1)

91-106: Guard against empty region and avoid awaiting router.push.

  • With ??, an empty string passes through; add a guard before mutating.
  • router.push is synchronous in next/navigation; remove await.
- addDomainMutation.mutate(+ const region = singleRegion ?? values.region;+ if (!region) {+ domainForm.setError("region", { message: "Region is required" });+ return;+ }+ addDomainMutation.mutate(
{
- name: values.domain,- region: singleRegion ?? values.region,+ name: values.domain,+ region,
},
{
onSuccess: async (data) => {
utils.domain.domains.invalidate();
- await router.push(`/domains/${data.id}`);+ router.push(`/domains/${data.id}`);
setOpen(false);
},
♻️ Duplicate comments (2)
apps/web/src/components/settings/AddSesSettings.tsx (1)

25-26: Enforce integer/bounds for sendRate and transactionalQuota.

Zero/float values will cause confusing behavior in queue concurrency.

Apply:

- sendRate: z.coerce.number(),- transactionalQuota: z.coerce.number().min(0).max(100),+ sendRate: z.coerce.number().int().min(1, "Send rate must be at least 1"),+ transactionalQuota: z.coerce.number().int().min(0).max(100),
apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx (1)

8-8: Fix lucide icon name: SendHorizonal → SendHorizontal (compile-time error).

lucide-react exports SendHorizontal; the current misspelling breaks build.

-import { SendHorizonal } from "lucide-react";+import { SendHorizontal } from "lucide-react";
...
- <SendHorizonal className="h-4 w-4 mr-2" />+ <SendHorizontal className="mr-2 h-4 w-4" />

Also applies to: 39-39

🧹 Nitpick comments (9)
apps/web/src/components/settings/AddSesSettings.tsx (3)

12-19: Avoid deep imports from @usesend/ui; use public entrypoints.

Deep-importing from /src can break with packaging/treeshaking. Prefer the package’s public exports (e.g., @usesend/ui). Please verify the right entrypoints before changing.


68-80: Align client URL checks with server rules and tighten copy.

Client enforces https/no-localhost; server currently only z.string().url(). Add the same https restriction server-side (see admin.ts), and tweak the message casing.

Apply:

- form.setError("usesendUrl", {- message: "URL must start with https://",- });+ form.setError("usesendUrl", {+ message: "URL must start with https://",+ });
...
- form.setError("usesendUrl", {- message: "URL must be a valid url",- });+ form.setError("usesendUrl", {+ message: "URL must be a valid URL",+ });

90-94: Explicitly ignore invalidate() promise to avoid unhandled warnings.

- utils.admin.invalidate();+ void utils.admin.invalidate();
apps/web/src/server/service/ses-settings-service.ts (1)

86-91: Rebrand strings still use “unsend”. Confirm intent or rename.

If backward-compat isn’t required for new resources, switch to “usesend”.

- const topicName = `${idPrefix}-${region}-unsend`;+ const topicName = `${idPrefix}-${region}-usesend`;
...
- const configGeneral = `${setting.idPrefix}-${setting.region}-unsend-general`;+ const configGeneral = `${setting.idPrefix}-${setting.region}-usesend-general`;
...
- const configClick = `${setting.idPrefix}-${setting.region}-unsend-click`;+ const configClick = `${setting.idPrefix}-${setting.region}-usesend-click`;
...
- const configOpen = `${setting.idPrefix}-${setting.region}-unsend-open`;+ const configOpen = `${setting.idPrefix}-${setting.region}-usesend-open`;
...
- const configFull = `${setting.idPrefix}-${setting.region}-unsend-full`;+ const configFull = `${setting.idPrefix}-${setting.region}-usesend-full`;

Also applies to: 227-233, 235-241, 243-249, 251-257

apps/web/src/app/(dashboard)/domains/add-domain.tsx (1)

3-4: Avoid deep /src imports from the UI package.

Import from the package’s public entrypoints to prevent brittle coupling to internals and to keep builds/publishing sane.

-import { Button } from "@usesend/ui/src/button";-import { Input } from "@usesend/ui/src/input";+import { Button } from "@usesend/ui/button";+import { Input } from "@usesend/ui/input";
@@
-} from "@usesend/ui/src/dialog";+} from "@usesend/ui/dialog";
@@
-} from "@usesend/ui/src/form";+} from "@usesend/ui/form";
@@
-} from "@usesend/ui/src/select";+} from "@usesend/ui/select";-import { toast } from "@usesend/ui/src/toaster";+import { toast } from "@usesend/ui/toaster";

If these entrypoints aren’t exported today, consider adding proper exports in the package rather than importing from src/*.

Also applies to: 11-11, 20-20, 36-37

apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx (4)

9-9: Remove leftover comment noise.

The inline note about removed code isn’t useful at runtime.

-// Removed dialog and example code. Clicking the button now sends the email directly.

27-30: Type-safe error handling in onError.

Accessing err.message assumes an Error. Narrow first to avoid runtime/type issues.

- onError: (err) => {- toast.error(err.message || "Failed to send test email");- },+ onError: (err) => {+ const msg = err instanceof Error ? err.message : "Failed to send test email";+ toast.error(msg);+ },

35-37: Add accessibility and form-safety props to Button.

type="button" prevents accidental form submit; aria-busy improves a11y.

- <Button- onClick={handleSendTestEmail}- disabled={sendTestEmailFromDomainMutation.isPending}- >+ <Button+ type="button"+ aria-busy={sendTestEmailFromDomainMutation.isPending}+ onClick={handleSendTestEmail}+ disabled={sendTestEmailFromDomainMutation.isPending}+ >

1-1: Rename file to PascalCase per repo guideline.

Use SendTestMail.tsx for component files in apps/web.

#!/bin/bash
git mv apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx \
apps/web/src/app/(dashboard)/domains/[domainId]/SendTestMail.tsx
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 128031f and dd4c8d5.

📒 Files selected for processing (6)
  • apps/docs/mint.json (3 hunks)
  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx (2 hunks)
  • apps/web/src/app/(dashboard)/domains/add-domain.tsx (6 hunks)
  • apps/web/src/components/settings/AddSesSettings.tsx (5 hunks)
  • apps/web/src/server/api/routers/admin.ts (4 hunks)
  • apps/web/src/server/service/ses-settings-service.ts (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/docs/mint.json
🧰 Additional context used
📓 Path-based instructions (8)
{apps,packages}/**/*.{js,jsx,ts,tsx,css,scss,md,mdx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier with the Tailwind plugin for code formatting

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx
  • apps/web/src/server/service/ses-settings-service.ts
  • apps/web/src/server/api/routers/admin.ts
{apps,packages}/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{js,jsx,ts,tsx}: Group imports by source (internal/external) and alphabetize them
Use camelCase for variables and functions, PascalCase for components and classes
Use try/catch with specific error types for error handling

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx
  • apps/web/src/server/service/ses-settings-service.ts
  • apps/web/src/server/api/routers/admin.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{ts,tsx}: Use strong typing in TypeScript, avoid any, and use Zod for validation
Follow Vercel style guides with strict TypeScript

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx
  • apps/web/src/server/service/ses-settings-service.ts
  • apps/web/src/server/api/routers/admin.ts
{apps,packages}/**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{jsx,tsx}: Use functional React components with hooks and group related hooks together
In React components, structure code with props at the top, hooks next, helper functions, then JSX

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use tRPC for internal API endpoints

In the web app, use the / alias for src imports (e.g., import { x } from "/utils/x")

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx
  • apps/web/src/server/service/ses-settings-service.ts
  • apps/web/src/server/api/routers/admin.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Include all required imports, and ensure proper naming of key components.

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx
  • apps/web/src/server/service/ses-settings-service.ts
  • apps/web/src/server/api/routers/admin.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation and semicolons (enforced by Prettier) in TypeScript files

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx
  • apps/web/src/server/service/ses-settings-service.ts
  • apps/web/src/server/api/routers/admin.ts
{apps/web,apps/marketing,packages/ui,packages/email-editor}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Name React component files in PascalCase (e.g., AppSideBar.tsx)

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
  • apps/web/src/components/settings/AddSesSettings.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx
🧬 Code graph analysis (2)
apps/web/src/server/service/ses-settings-service.ts (2)
apps/web/src/env.js (2)
  • env (5-125)
  • env (5-125)
apps/web/src/server/service/email-queue-service.ts (1)
  • EmailQueueService (37-302)
apps/web/src/server/api/routers/admin.ts (1)
apps/web/src/server/service/ses-settings-service.ts (1)
  • SesSettingsService (22-212)
🔇 Additional comments (5)
apps/web/src/server/api/routers/admin.ts (1)

62-67: LGTM: sensible defaulting to env.AWS_DEFAULT_REGION.

apps/web/src/app/(dashboard)/domains/add-domain.tsx (1)

160-195: Conditional Region field rendering looks good.

Cleanly hides the selector for single-region projects and preserves validation messaging when shown.

apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx (3)

23-25: No changes needed: utils.domain.domains.invalidate() correctly matches the domains query in your domain router.


5-5: Confirm automatic JSX runtime before removing React import
The import React from "react"; can be omitted under the new JSX transform—but first verify that the shared @usesend/typescript-config/nextjs.json sets compilerOptions.jsx to "react-jsx". If confirmed, drop this import.


3-3: Retain current deep imports: Button and toast aren’t exported in packages/ui/index.ts, so import { Button } from "@usesend/ui/button" and import { toast } from "@usesend/ui/toaster" will fail.

Likely an incorrect or invalid review comment.

Comment threadapps/web/src/app/(dashboard)/domains/add-domain.tsx
Comment on lines 26 to 32
z.object({
region: z.string(),
unsendUrl: z.string().url(),
usesendUrl: z.string().url(),
sendRate: z.number(),
transactionalQuota: z.number(),
})
}),
)

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.

🛠️ Refactor suggestion

Harden input schema: enforce https and integer/bounds.

Keep server validation at least as strict as client.

 .input(
z.object({
region: z.string(),
- usesendUrl: z.string().url(),- sendRate: z.number(),- transactionalQuota: z.number(),+ usesendUrl: z+ .string()+ .url()+ .refine((u) => u.startsWith("https://"), "URL must use https"),+ sendRate: z.number().int().min(1),+ transactionalQuota: z.number().int().min(0).max(100),
}),
)
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
z.object({
region: z.string(),
unsendUrl: z.string().url(),
usesendUrl: z.string().url(),
sendRate: z.number(),
transactionalQuota: z.number(),
})
}),
)
.input(
z.object({
region: z.string(),
usesendUrl: z
.string()
.url()
.refine((u)=>u.startsWith("https://"),"URL must use https"),
sendRate: z.number().int().min(1),
transactionalQuota: z.number().int().min(0).max(100),
}),
)

ghost 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.

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/web/src/app/(dashboard)/domains/add-domain.tsx (1)

41-46: Schema change resolves the hidden-required-field issue.

Making region optional and validating at submit removes the earlier failure when the selector is hidden for a single region.

🧹 Nitpick comments (5)
apps/web/src/app/(dashboard)/domains/add-domain.tsx (5)

3-4: Align imports with repo guidelines (grouped and alphabetized).

Per coding guidelines, group external/internal imports and alphabetize. Apply across the file to keep diffs tidy.

Also applies to: 11-11, 20-20, 31-37


69-73: Handle zero-region state explicitly.

If the API ever returns 0 regions, the selector is hidden and users can’t correct the “Region is required” error. Consider surfacing a visible message or disabling submit until regions load/exist.


84-90: Surface an actionable error when region is missing but the field is hidden.

Add a toast so users get feedback even when the Region field isn’t rendered.

- if (!values.region && !singleRegion) {- domainForm.setError("region", {- message: "Region is required",- });- return;- }+ if (!values.region && !singleRegion) {+ domainForm.setError("region", { message: "Region is required" });+ toast.error("Region is required");+ return;+ }

96-101: Avoid sending an empty string for region.

Let types catch missing values instead of passing "".

- region: singleRegion ?? values.region ?? "",+ region: singleRegion ?? values.region,

202-212: Guard submit while regions are loading or unavailable.

Prevents user confusion and unreachable submits if regions aren’t ready/empty.

- <Button- className=" w-[100px]"- type="submit"- disabled={- addDomainMutation.isPending || limitsQuery.isLoading- }- >+ <Button+ className="w-[100px]"+ type="submit"+ disabled={+ addDomainMutation.isPending ||+ limitsQuery.isLoading ||+ regionQuery.isLoading ||+ ((regionQuery.data?.length ?? 0) === 0)+ }+ >
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between dd4c8d5 and cf8c89f.

📒 Files selected for processing (3)
  • apps/web/src/app/(dashboard)/domains/add-domain.tsx (7 hunks)
  • apps/web/src/server/service/campaign-service.ts (3 hunks)
  • packages/email-editor/src/extensions/SlashCommand.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/email-editor/src/extensions/SlashCommand.tsx
  • apps/web/src/server/service/campaign-service.ts
🧰 Additional context used
📓 Path-based instructions (8)
{apps,packages}/**/*.{js,jsx,ts,tsx,css,scss,md,mdx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier with the Tailwind plugin for code formatting

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
{apps,packages}/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{js,jsx,ts,tsx}: Group imports by source (internal/external) and alphabetize them
Use camelCase for variables and functions, PascalCase for components and classes
Use try/catch with specific error types for error handling

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{ts,tsx}: Use strong typing in TypeScript, avoid any, and use Zod for validation
Follow Vercel style guides with strict TypeScript

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
{apps,packages}/**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{jsx,tsx}: Use functional React components with hooks and group related hooks together
In React components, structure code with props at the top, hooks next, helper functions, then JSX

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use tRPC for internal API endpoints

In the web app, use the / alias for src imports (e.g., import { x } from "/utils/x")

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Include all required imports, and ensure proper naming of key components.

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation and semicolons (enforced by Prettier) in TypeScript files

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
{apps/web,apps/marketing,packages/ui,packages/email-editor}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Name React component files in PascalCase (e.g., AppSideBar.tsx)

Files:

  • apps/web/src/app/(dashboard)/domains/add-domain.tsx
🧬 Code graph analysis (1)
apps/web/src/app/(dashboard)/domains/add-domain.tsx (2)
packages/ui/src/form.tsx (6)
  • FormField (176-176)
  • FormItem (171-171)
  • FormLabel (172-172)
  • FormControl (173-173)
  • FormMessage (175-175)
  • FormDescription (174-174)
packages/ui/src/select.tsx (5)
  • Select (150-150)
  • SelectTrigger (153-153)
  • SelectValue (152-152)
  • SelectContent (154-154)
  • SelectItem (156-156)
🔇 Additional comments (1)
apps/web/src/app/(dashboard)/domains/add-domain.tsx (1)

165-201: Disable the Select via SelectTrigger, not Select.

In shadcn-style Select, disabled belongs on SelectTrigger. The current prop on Select is likely a no-op.

- <Select- onValueChange={field.onChange}- value={field.value}- disabled={regionQuery.isLoading}- >+ <Select onValueChange={field.onChange} value={field.value}>
<FormControl>
- <SelectTrigger>+ <SelectTrigger disabled={regionQuery.isLoading}>
<SelectValue placeholder="Select region" />
</SelectTrigger>
</FormControl>

Likely an incorrect or invalid review comment.

@KMKoushik
KMKoushik merged commit 07c53d3 into mainSep 2, 2025
Sign up for freeto 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

@KMKoushik