Uh oh!
There was an error while loading. Please reload this page.
rebrand to useSend - #210
Conversation
Caution Review failedThe pull request is closed. WalkthroughProject-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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
✨ Finishing Touches🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File ( |
There was a problem hiding this comment.
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.devWhile the Node.js and Python examples have been updated to use
usesend.com, the PHP, Ruby, and cURL examples still reference the oldunsend.devdomain.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 usageWhile line 14 uses the new pattern
env.USESEND_API_KEY ?? env.UNSEND_API_KEY, line 111 only checks forenv.UNSEND_API_KEY. This could cause issues if onlyUSESEND_API_KEYis 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 contentThe 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 ofanyAccording to the coding guidelines, avoid using
anyin TypeScript. Useunknownor 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.accessis moot. If this is workspace-only, keeping it private is fine; if external consumers need it, flip"private": falseand publish under the new scope.Would you like me to prep a follow-up PR to either (a) remove
publishConfigfor 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 onlydistfor 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.tocontains 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
stackto 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
emailEventexists; 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=messageIdto 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 consistencyThe 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 consistencyThe function
isUNSENDErrorResponsestill 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.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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.tsapps/web/src/server/mailer.tsapps/web/src/server/service/ses-hook-parser.tsapps/web/src/server/aws/ses.tsapps/web/src/lib/constants/example-codes.tsapps/web/src/env.jspackages/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.tsapps/web/src/server/mailer.tsapps/web/src/server/service/ses-hook-parser.tsapps/web/src/server/aws/ses.tsapps/web/src/lib/constants/example-codes.tsapps/web/src/env.jspackages/sdk/src/unsend.ts
{apps,packages}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
{apps,packages}/**/*.{ts,tsx}: Use strong typing in TypeScript, avoidany, and use Zod for validation
Follow Vercel style guides with strict TypeScript
Files:
packages/sdk/index.tsapps/web/src/server/mailer.tsapps/web/src/server/service/ses-hook-parser.tsapps/web/src/server/aws/ses.tsapps/web/src/lib/constants/example-codes.tspackages/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.tsapps/web/src/server/mailer.tsapps/web/src/server/service/ses-hook-parser.tsapps/web/src/server/aws/ses.tsapps/web/src/lib/constants/example-codes.tsapps/web/src/env.jspackages/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.tsapps/web/src/server/mailer.tsapps/web/src/server/service/ses-hook-parser.tsapps/web/src/server/aws/ses.tsapps/web/src/lib/constants/example-codes.tspackages/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.tsapps/web/src/server/service/ses-hook-parser.tsapps/web/src/server/aws/ses.tsapps/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.jsonapps/web/tsconfig.jsonapps/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.jsonpackages/email-editor/package.jsonpackages/ui/package.jsonapps/marketing/package.jsonpackages/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.tsapps/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: unsend → usesend - scripts r...
(QB_NEW_EN)
[grammar] ~22-~22: There might be a mistake here.
Context: ...ot package.json: - name: unsend → usesend - 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 (unsend → usesend). - 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 unsend→usesend`. ...
(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: ...v→docs.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
Verifiedpackages/typescript-config/base.jsonis present.packages/sdk/package.json (1)
2-2: Deprecated alias present – no action needed.
Theexport { 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-editorinstead of the old@unsend/email-editorscope.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.jsto@usesend/eslint-config/react-internal.js, maintaining consistency with the rebranding effort.packages/ui/tsconfig.lint.json (1)
2-2: Update leftover@unsend/typescript-configextends 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 TypeScriptpackages/email-editor/tsconfig.lint.json (1)
2-2: Confirmed shared config exposes react-library.json. The file exists atpackages/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-configand no.eslintrc*extend@unsendconfigs.apps/marketing/.eslintrc.cjs (1)
4-4: No remaining @unsend/eslint-config extends found
Ranrg -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@unsendreferences, 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 rebrandExtends path update to
@usesend/eslint-config/react-internal.jsis correct; run:pnpm --filter @usesend/email-editor exec eslint --print-config src/index.ts > /tmp/eslint-usesend.jsonand compare the output against the previous
react-internal.jsconfig.packages/sdk/.eslintrc.cjs (1)
4-4: Extends path rebrand is fine; monorepo uses a singleworkspace:*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 defaultimport Unsend from "unsend"occurrences were found.apps/web/src/server/aws/ses.ts (2)
236-238: Good backward compatibility with dual headersSetting both
X-Usesend-Email-IDandX-Unsend-Email-IDheaders 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 improvementsThe 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 keyUsing
env.USESEND_API_KEY ?? env.UNSEND_API_KEYprovides 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 compatibilityThe 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 implementationThe 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.
| module.exports = { | ||
| ignorePatterns: ["apps/**", "packages/**"], | ||
| extends: ["@unsend/eslint-config/library.js"], | ||
| extends: ["@usesend/eslint-config/library.js"], |
There was a problem hiding this comment.
💡 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-configLength 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.
| "@usesend/email-editor": "workspace:*", | ||
| "@usesend/ui": "workspace:*", |
There was a problem hiding this comment.
💡 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", | |||
There was a problem hiding this comment.
💡 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' -SLength 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.
| "@usesend/email-editor": "workspace:*", | ||
| "@usesend/ui": "workspace:*", |
There was a problem hiding this comment.
💡 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.
| "tldts": "^7.0.4", | ||
| "ua-parser-js": "^2.0.3", | ||
| "unsend": "workspace:*", | ||
| "usesend": "workspace:*", |
There was a problem hiding this comment.
💡 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.
| @@ -1,5 +1,5 @@ | |||
| { | |||
| "name": "unsend", | |||
| "name": "usesend", | |||
There was a problem hiding this comment.
💡 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", | |||
There was a problem hiding this comment.
💡 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' -SLength 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", | |||
There was a problem hiding this comment.
💡 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/**' -C2Length 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", | |||
There was a problem hiding this comment.
💡 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.
| "@usesend/eslint-config": "workspace:*", | ||
| "@usesend/tailwind-config": "workspace:*", | ||
| "@usesend/typescript-config": "workspace:*", | ||
| "eslint": "^8.57.1", | ||
| "postcss": "^8.5.3", |
There was a problem hiding this comment.
💡 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 | nlLength 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.jsonLength of output: 3886
Align ESLint major version and add React peer dependencies in packages/ui/package.json
- In devDependencies, bump
"eslint"from^8.57.1to^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.
| "@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.
There was a problem hiding this comment.
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 paragraphFix 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 siteapps/web/src/app/(dashboard)/emails/email-details.tsx (3)
9-10: Client component importing Prisma at runtime — switch to type-only importsThis 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 undefinedemailEventsand avoid crash
emailQuery.data?.emailEvents.mapwill throw whenemailEventsis 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
idisn’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)andmetrics?.complaintRate.toFixed(2)will crash whenmetricsis undefined becausetoFixedis invoked onundefined.- {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, andphpCodeare 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.mapwill throw because.mapis 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.
fleprevents flex layout; useflex.- <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 UseSendPR 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 allunsend.devreferences
- Change
<Link href="https://docs.unsend.dev"…>tohttps://docs.usesend.devin apps/web/src/components/AppSideBar.tsx:236–241.- Replace every occurrence of
unsend.devwithusesend.devand update branding fromUnsendtoUseSendacross 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 UseSendBranding 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 forundefined, 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
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| 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); |
There was a problem hiding this comment.
🛠️ 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.
| 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"; |
There was a problem hiding this comment.
💡 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 -SLength 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"; |
There was a problem hiding this comment.
💡 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'||trueLength 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.
| 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.
| `${path.join(require.resolve("@usesend/ui"), "..")}/**/*.{ts,tsx}`, | ||
| `${path.join(require.resolve("@usesend/email-editor"), "..")}/**/*.{ts,tsx}`, |
There was a problem hiding this comment.
💡 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"doneLength 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.jsonLength 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"; |
There was a problem hiding this comment.
💡 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 (cnbefore local), update<h3>…Unsend…to “UseSend”, change sample link tohttps://usesend.dev, renameconst unsend = new Unsendtoconst usesend = new UseSend, and update class names tousesend-prose/usesend-editor. - In
packages/email-editor/src/styles/index.css: rename all.unsend-editor→.usesend-editorand.unsend-prose→.usesend-prose. - In
packages/email-editor/package.json: update thedescriptionfield to reference “UseSend” instead of “unsend”. - In
packages/email-editor/src/extensions/*: rename alldata-unsend-componentattributes todata-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.
| <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> |
There was a problem hiding this comment.
💡 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'doneLength 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.
| <img src="https://github.com/usesend/usesend/assets/24666922/68c41a6f-8fd1-4a3e-8d9b-987dda105c22" style="width: 100%;" /> | ||
| </div> |
There was a problem hiding this comment.
🛠️ 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.
| <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.
| 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 | ||
There was a problem hiding this comment.
🛠️ 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.
| 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.
There was a problem hiding this comment.
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.
📒 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, avoidany, 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 orgusesendconfirmed—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 atdocker/Dockerfileandapps/smtp-server/Dockerfileexist.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:latesttousesend/smtp-proxy:latestto 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.tsLine 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.tsLine 11.apps/smtp-server/src/server.ts (7)
9-11: LGTM: Environment defaults updated for rebranding.The default values for
AUTH_USERNAMEandUNSEND_BASE_URLhave 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
sendEmailToUnsendtosendEmailToUseSendto 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
sendEmailToUseSendfunction, 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.
There was a problem hiding this comment.
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/newlinesDirectly 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 clarityEnsure 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 itThis 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 payloadAvoid 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_jsonapps/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 andapp.usesend.comdomain:
- 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” andhttps://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” toX-Usesend-Email-ID/“useSend URL”.- apps/web/src/server/aws/ses.ts: remove
X-Unsend-Email-IDheader and useX-Usesend-Email-IDonly.- 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” andapp.usesend.com.Run a final grep for
/unsend/ito 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
securitysection 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}/verifydeclaresidasrequired: falseandnullable: 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 URLhttps://docs.usesend.com/get-started/create-aws-credentialsis 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 goodThe 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 localMove @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 theunsendpackage:
+
+bash +npm uninstall unsend +npm install usesend +
+
+The SDK provides a temporary alias export forUnsend, 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 srcImporting 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: falseto 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 viapnpm tsxwithout CommonJS.If the repo uses ESM, replace
require.mainguard 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 composeCLI.-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+## PrerequisitesAlso 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 contextThe 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 syntaxPrefer 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 tagsConsider 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:latestAlso 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 brandUse “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 refProvide a concrete ref type.
- const menuContainerRef = useRef(null);+ const menuContainerRef = useRef<HTMLDivElement | null>(null);
70-72: Use string[] over ArrayAligns with common TS style.
- variables?: Array<string>;+ variables?: string[];apps/web/src/lib/constants/ses-errors.ts (1)
30-33: Polish wording in Suppressed messageLowercase “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_startedapps/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 -dapps/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:roUsage: 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.
📒 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.tsxapps/web/src/server/email-templates/TeamInviteEmail.tsxapps/web/src/app/api/dev/email-preview/route.tsapps/web/src/server/email-templates/components/EmailHeader.tsxapps/web/src/server/service/domain-service.tsapps/docs/introduction.mdxapps/docs/api-reference/introduction.mdxapps/web/src/server/email-templates/test.tsapps/web/src/server/email-templates/components/EmailFooter.tsxapps/docs/get-started/set-up-docker.mdxapps/smtp-server/src/usage.jsapps/docs/get-started/smtp.mdxpackages/sdk/README.mdapps/web/src/lib/constants/ses-errors.tsapps/docs/guides/use-with-react-email.mdxapps/web/src/lib/constants/example-codes.tsapps/docs/get-started/local.mdxapps/docs/get-started/self-hosting.mdxapps/docs/get-started/nodejs.mdxapps/web/src/server/mailer.tspackages/email-editor/src/editor.tsxapps/smtp-server/src/server.tsapps/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.tsxapps/web/src/server/email-templates/TeamInviteEmail.tsxapps/web/src/app/api/dev/email-preview/route.tsapps/web/src/server/email-templates/components/EmailHeader.tsxapps/web/src/server/service/domain-service.tsapps/web/src/server/email-templates/test.tsapps/web/src/server/email-templates/components/EmailFooter.tsxapps/smtp-server/src/usage.jsapps/web/src/lib/constants/ses-errors.tsapps/web/src/lib/constants/example-codes.tsapps/web/src/server/mailer.tspackages/email-editor/src/editor.tsxapps/smtp-server/src/server.tsapps/web/src/app/unsubscribe/page.tsx
{apps,packages}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
{apps,packages}/**/*.{ts,tsx}: Use strong typing in TypeScript, avoidany, and use Zod for validation
Follow Vercel style guides with strict TypeScript
Files:
apps/web/src/server/email-templates/OtpEmail.tsxapps/web/src/server/email-templates/TeamInviteEmail.tsxapps/web/src/app/api/dev/email-preview/route.tsapps/web/src/server/email-templates/components/EmailHeader.tsxapps/web/src/server/service/domain-service.tsapps/web/src/server/email-templates/test.tsapps/web/src/server/email-templates/components/EmailFooter.tsxapps/web/src/lib/constants/ses-errors.tsapps/web/src/lib/constants/example-codes.tsapps/web/src/server/mailer.tspackages/email-editor/src/editor.tsxapps/smtp-server/src/server.tsapps/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.tsxapps/web/src/server/email-templates/TeamInviteEmail.tsxapps/web/src/server/email-templates/components/EmailHeader.tsxapps/web/src/server/email-templates/components/EmailFooter.tsxpackages/email-editor/src/editor.tsxapps/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.tsxapps/web/src/server/email-templates/TeamInviteEmail.tsxapps/web/src/app/api/dev/email-preview/route.tsapps/web/src/server/email-templates/components/EmailHeader.tsxapps/web/src/server/service/domain-service.tsapps/web/src/server/email-templates/test.tsapps/web/src/server/email-templates/components/EmailFooter.tsxapps/web/src/lib/constants/ses-errors.tsapps/web/src/lib/constants/example-codes.tsapps/web/src/server/mailer.tsapps/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.tsxapps/web/src/server/email-templates/TeamInviteEmail.tsxapps/web/src/app/api/dev/email-preview/route.tsapps/web/src/server/email-templates/components/EmailHeader.tsxapps/web/src/server/service/domain-service.tsapps/web/src/server/email-templates/test.tsapps/web/src/server/email-templates/components/EmailFooter.tsxapps/smtp-server/src/usage.jsapps/web/src/lib/constants/ses-errors.tsapps/web/src/lib/constants/example-codes.tsapps/web/src/server/mailer.tspackages/email-editor/src/editor.tsxapps/smtp-server/src/server.tsapps/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.tsxapps/web/src/server/email-templates/TeamInviteEmail.tsxapps/web/src/app/api/dev/email-preview/route.tsapps/web/src/server/email-templates/components/EmailHeader.tsxapps/web/src/server/service/domain-service.tsapps/web/src/server/email-templates/test.tsapps/web/src/server/email-templates/components/EmailFooter.tsxapps/web/src/lib/constants/ses-errors.tsapps/web/src/lib/constants/example-codes.tsapps/web/src/server/mailer.tspackages/email-editor/src/editor.tsxapps/smtp-server/src/server.tsapps/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.tsxapps/web/src/server/email-templates/TeamInviteEmail.tsxapps/web/src/server/email-templates/components/EmailHeader.tsxapps/web/src/server/email-templates/components/EmailFooter.tsxpackages/email-editor/src/editor.tsxapps/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.tsxapps/web/src/server/email-templates/test.tsapps/smtp-server/src/usage.jsapps/docs/get-started/smtp.mdxapps/web/src/server/mailer.tsapps/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
TheUseSendconstructor 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:
- Change
[useSend API key]→[UseSend API key]and[Verified domain]→[Verified Domain].- Confirm https://app.usesend.com/dev-settings/api-keys and https://app.usesend.com/domains still match current dashboard routes.
- Audit other
useSendmentions in the README for properUseSendcasing.apps/web/src/lib/constants/example-codes.ts (3)
20-20: Verify SDK export shape (named vs default) to avoid runtime import errorsConfirm whether UseSend is a named or default export.
If default, switch to:
- import { UseSend } from "usesend";+ import UseSend from "usesend";
24-24: Confirm baseUrl exampleValidate 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 examplesSince 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-examplesfor 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 replacementsString 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
Rundocker compose versionin your local/dev/CI environments and ensure it reports v2.x (required for top-levelname: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.Zor the Docker Hub API) before merging.
| 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. | ||
There was a problem hiding this comment.
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.
| 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.
| host: "smtp.usesend.com", | ||
| port: 465, | ||
| secure: false, | ||
| auth: { | ||
| user: "unsend", | ||
| user: "usesend", | ||
| pass: "us_123", | ||
| }, |
There was a problem hiding this comment.
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.
| 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." |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| 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.
| 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 | ||
There was a problem hiding this comment.
🛠️ 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.
| <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> |
There was a problem hiding this comment.
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.
| <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.
| 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}"}'`, |
There was a problem hiding this comment.
🛠️ 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.
| 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`, | ||
| }); |
There was a problem hiding this comment.
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.
| 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".
There was a problem hiding this comment.
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 invalidfindUniqueusageIn apps/web/src/server/service/domain-service.ts (lines 37–39),
findUniqueonly accepts a single unique field or a defined composite key. Sincenameis already unique, dropteamIdfrom thewhereclause:- 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.
📒 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, avoidany, 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
There was a problem hiding this comment.
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-monopick 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.
⛔ Files ignored due to path filters (14)
apps/web/public/Logo-rounded.pngis excluded by!**/*.pngapps/web/public/favicon.icois excluded by!**/*.icoapps/web/public/favicon_io/android-chrome-192x192.pngis excluded by!**/*.pngapps/web/public/favicon_io/android-chrome-512x512.pngis excluded by!**/*.pngapps/web/public/favicon_io/apple-touch-icon.pngis excluded by!**/*.pngapps/web/public/favicon_io/favicon-16x16.pngis excluded by!**/*.pngapps/web/public/favicon_io/favicon-32x32.pngis excluded by!**/*.pngapps/web/public/favicon_io/favicon.icois excluded by!**/*.icoapps/web/public/logo-dark.pngis excluded by!**/*.pngapps/web/public/logo-dark.svgis excluded by!**/*.svgapps/web/public/logo-full-wordmark.svgis excluded by!**/*.svgapps/web/public/logo-light.pngis excluded by!**/*.pngapps/web/public/logo-light.svgis excluded by!**/*.svgapps/web/public/logo-squircle.pngis 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.tsapps/web/src/app/layout.tsxapps/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.tsapps/web/src/app/layout.tsxapps/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, avoidany, and use Zod for validation
Follow Vercel style guides with strict TypeScript
Files:
packages/tailwind-config/tailwind.config.tsapps/web/src/app/layout.tsxapps/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.tsapps/web/src/app/layout.tsxapps/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.tsapps/web/src/app/layout.tsxapps/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.tsxapps/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.tsxapps/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.tsxapps/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/googleimports:-import { Inter } from "next/font/google";-import { JetBrains_Mono } from "next/font/google";+import { Inter, JetBrains_Mono } from "next/font/google";- The
Toastercomponent isn't exported from@usesend/ui’s public entrypoint (packages/ui/index.ts), soimport { 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.
| title: "useSend", | ||
| description: "Open source email platoform", | ||
| icons: [{ rel: "icon", url: "/favicon.ico" }], |
There was a problem hiding this comment.
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.
| 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"; |
There was a problem hiding this comment.
🛠️ 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.
| 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.
| const finalCallbackUrl = inviteId | ||
| ? `/join-team?inviteId=${inviteId}` | ||
| : `${callbackUrl}/dashboard`; |
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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.htmlcontains 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/uifor all of the above. Also verify whetherSpinneris 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 → unsendUrlis correct pending server migration. Please confirm the server still expectsunsendUrl; plan to flip this once backend renames land. Optionally simplify flow withmutateAsyncand 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/uifirst, 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 toCampaignsPagefor 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:asChildis 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 sharedTypographyPropsand 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.
📒 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.tsxapps/web/src/app/(dashboard)/contacts/page.tsxpackages/ui/index.tsapps/web/src/app/(dashboard)/campaigns/page.tsxapps/web/src/app/(dashboard)/dashboard/page.tsxapps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsxapps/web/src/app/(dashboard)/emails/page.tsxapps/web/src/app/(dashboard)/admin/page.tsxapps/web/src/app/(dashboard)/domains/page.tsxpackages/ui/src/typography.tsxapps/web/src/app/(dashboard)/templates/page.tsxapps/web/src/app/(dashboard)/domains/[domainId]/page.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsxpackages/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.tsxapps/web/src/app/(dashboard)/contacts/page.tsxpackages/ui/index.tsapps/web/src/app/(dashboard)/campaigns/page.tsxapps/web/src/app/(dashboard)/dashboard/page.tsxapps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsxapps/web/src/app/(dashboard)/emails/page.tsxapps/web/src/app/(dashboard)/admin/page.tsxapps/web/src/app/(dashboard)/domains/page.tsxpackages/ui/src/typography.tsxapps/web/src/app/(dashboard)/templates/page.tsxapps/web/src/app/(dashboard)/domains/[domainId]/page.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsxpackages/ui/src/sheet.tsx
{apps,packages}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
{apps,packages}/**/*.{ts,tsx}: Use strong typing in TypeScript, avoidany, and use Zod for validation
Follow Vercel style guides with strict TypeScript
Files:
apps/web/src/app/(dashboard)/dev-settings/page.tsxapps/web/src/app/(dashboard)/contacts/page.tsxpackages/ui/index.tsapps/web/src/app/(dashboard)/campaigns/page.tsxapps/web/src/app/(dashboard)/dashboard/page.tsxapps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsxapps/web/src/app/(dashboard)/emails/page.tsxapps/web/src/app/(dashboard)/admin/page.tsxapps/web/src/app/(dashboard)/domains/page.tsxpackages/ui/src/typography.tsxapps/web/src/app/(dashboard)/templates/page.tsxapps/web/src/app/(dashboard)/domains/[domainId]/page.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsxpackages/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.tsxapps/web/src/app/(dashboard)/contacts/page.tsxapps/web/src/app/(dashboard)/campaigns/page.tsxapps/web/src/app/(dashboard)/dashboard/page.tsxapps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsxapps/web/src/app/(dashboard)/emails/page.tsxapps/web/src/app/(dashboard)/admin/page.tsxapps/web/src/app/(dashboard)/domains/page.tsxpackages/ui/src/typography.tsxapps/web/src/app/(dashboard)/templates/page.tsxapps/web/src/app/(dashboard)/domains/[domainId]/page.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsxpackages/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.tsxapps/web/src/app/(dashboard)/contacts/page.tsxapps/web/src/app/(dashboard)/campaigns/page.tsxapps/web/src/app/(dashboard)/dashboard/page.tsxapps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsxapps/web/src/app/(dashboard)/emails/page.tsxapps/web/src/app/(dashboard)/admin/page.tsxapps/web/src/app/(dashboard)/domains/page.tsxapps/web/src/app/(dashboard)/templates/page.tsxapps/web/src/app/(dashboard)/domains/[domainId]/page.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/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.tsxapps/web/src/app/(dashboard)/contacts/page.tsxpackages/ui/index.tsapps/web/src/app/(dashboard)/campaigns/page.tsxapps/web/src/app/(dashboard)/dashboard/page.tsxapps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsxapps/web/src/app/(dashboard)/emails/page.tsxapps/web/src/app/(dashboard)/admin/page.tsxapps/web/src/app/(dashboard)/domains/page.tsxpackages/ui/src/typography.tsxapps/web/src/app/(dashboard)/templates/page.tsxapps/web/src/app/(dashboard)/domains/[domainId]/page.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsxpackages/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.tsxapps/web/src/app/(dashboard)/contacts/page.tsxpackages/ui/index.tsapps/web/src/app/(dashboard)/campaigns/page.tsxapps/web/src/app/(dashboard)/dashboard/page.tsxapps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsxapps/web/src/app/(dashboard)/emails/page.tsxapps/web/src/app/(dashboard)/admin/page.tsxapps/web/src/app/(dashboard)/domains/page.tsxpackages/ui/src/typography.tsxapps/web/src/app/(dashboard)/templates/page.tsxapps/web/src/app/(dashboard)/domains/[domainId]/page.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsxpackages/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.tsxapps/web/src/app/(dashboard)/contacts/page.tsxapps/web/src/app/(dashboard)/campaigns/page.tsxapps/web/src/app/(dashboard)/dashboard/page.tsxapps/web/src/app/(dashboard)/dev-settings/api-keys/page.tsxapps/web/src/app/(dashboard)/emails/page.tsxapps/web/src/app/(dashboard)/admin/page.tsxapps/web/src/app/(dashboard)/domains/page.tsxpackages/ui/src/typography.tsxapps/web/src/app/(dashboard)/templates/page.tsxapps/web/src/app/(dashboard)/domains/[domainId]/page.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/web/src/app/(dashboard)/campaigns/[campaignId]/page.tsxpackages/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.tsxapps/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
H2andBodyTextare implemented insrc/typography.tsxand 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 anduse()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.
| value={`${domainQuery.data?.dkimSelector ?? "unsend"}._domainkey.${domainQuery.data?.subdomain || domainQuery.data?.name}`} | ||
| /> |
There was a problem hiding this comment.
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.
| 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.
| sendRate: z.coerce.number(), | ||
| transactionalQuota: z.coerce.number().min(0).max(100), |
There was a problem hiding this comment.
🛠️ 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.
left a comment
There was a problem hiding this comment.
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/parserand@typescript-eslint/eslint-pluginremain on major v8 (currently 8.31.0).apps/marketing/next.config.js (2)
11-11: Confirm module system;export defaultin.jsrequires ESM.If
apps/marketing/package.json(or repo root) doesn’t set"type": "module", Next will treatnext.config.jsas 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: Usenext.config.tsto 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
anyfromres.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.
⛔ Files ignored due to path filters (3)
apps/marketing/public/favicon.icois excluded by!**/*.icoapps/marketing/public/logo-squircle.pngis excluded by!**/*.pngpnpm-lock.yamlis 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.jsapps/marketing/src/components/GitHubStarsButton.tsxapps/marketing/tailwind.config.tsapps/marketing/src/app/page.tsxapps/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.jsapps/marketing/src/components/GitHubStarsButton.tsxapps/marketing/tailwind.config.tsapps/marketing/src/app/page.tsxapps/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.jsapps/marketing/src/components/GitHubStarsButton.tsxapps/marketing/tailwind.config.tsapps/marketing/src/app/page.tsxapps/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, avoidany, and use Zod for validation
Follow Vercel style guides with strict TypeScript
Files:
apps/marketing/src/components/GitHubStarsButton.tsxapps/marketing/tailwind.config.tsapps/marketing/src/app/page.tsxapps/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.tsxapps/marketing/src/app/page.tsxapps/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.tsxapps/marketing/tailwind.config.tsapps/marketing/src/app/page.tsxapps/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.tsxapps/marketing/src/app/page.tsxapps/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.cjsapps/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"todescription: "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 tokentext-blueexists (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 confirmed —apps/marketing/public/logo-squircle.pngexists; 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/*.
| <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> |
There was a problem hiding this comment.
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.
| <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.
| `${path.join(require.resolve("@usesend/ui"), "..")}/**/*.{ts,tsx}`, | ||
| `${path.join(require.resolve("@usesend/email-editor"), "..")}/**/*.{ts,tsx}`, |
There was a problem hiding this comment.
🛠️ 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.
left a comment
There was a problem hiding this comment.
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 awaitingrouter.push.
- With
??, an empty string passes through; add a guard before mutating.router.pushis synchronous innext/navigation; removeawait.- 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/srcimports 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.
📒 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.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsxapps/web/src/server/service/ses-settings-service.tsapps/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.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsxapps/web/src/server/service/ses-settings-service.tsapps/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, avoidany, and use Zod for validation
Follow Vercel style guides with strict TypeScript
Files:
apps/web/src/app/(dashboard)/domains/add-domain.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsxapps/web/src/server/service/ses-settings-service.tsapps/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.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/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.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsxapps/web/src/server/service/ses-settings-service.tsapps/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.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsxapps/web/src/server/service/ses-settings-service.tsapps/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.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsxapps/web/src/server/service/ses-settings-service.tsapps/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.tsxapps/web/src/components/settings/AddSesSettings.tsxapps/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 thedomainsquery in your domain router.
5-5: Confirm automatic JSX runtime before removing React import
Theimport React from "react";can be omitted under the new JSX transform—but first verify that the shared@usesend/typescript-config/nextjs.jsonsetscompilerOptions.jsxto"react-jsx". If confirmed, drop this import.
3-3: Retain current deep imports:Buttonandtoastaren’t exported inpackages/ui/index.ts, soimport { Button } from "@usesend/ui/button"andimport { toast } from "@usesend/ui/toaster"will fail.Likely an incorrect or invalid review comment.
Uh oh!
There was an error while loading. Please reload this page.
| z.object({ | ||
| region: z.string(), | ||
| unsendUrl: z.string().url(), | ||
| usesendUrl: z.string().url(), | ||
| sendRate: z.number(), | ||
| transactionalQuota: z.number(), | ||
| }) | ||
| }), | ||
| ) |
There was a problem hiding this comment.
🛠️ 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.
| 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), | |
| }), | |
| ) |
left a comment
There was a problem hiding this comment.
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
regionoptional 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.
📒 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, avoidany, 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,
disabledbelongs onSelectTrigger. The current prop onSelectis 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.
Summary by CodeRabbit
New Features
Improvements
Deprecations
Documentation
Chores
Removal