Skip to content

feat: expose domain dns records via api#259

Merged
KMKoushik merged 3 commits intomainfrom
codex/fix-api-domain-creation-issue
Sep 26, 2025
Merged

feat: expose domain dns records via api#259
KMKoushik merged 3 commits intomainfrom
codex/fix-api-domain-creation-issue

Conversation

@KMKoushik
Copy link
Copy Markdown
Member

@KMKoushik KMKoushik commented Sep 26, 2025

Summary

  • add DNS record generation to domain service responses and normalize verification metadata
  • expose DNS guidance in TRPC and public API responses, updating the OpenAPI schema
  • update the dashboard domain detail UI to consume API-provided DNS records and share domain types

Testing

  • pnpm lint --filter web (fails: existing warnings in packages/ui about unused variables)

https://chatgpt.com/codex/tasks/task_e_68d43c1206c88329b5e02c69fbae2067

Summary by CodeRabbit

  • New Features

    • Domain settings now render live DNS records (type, name, value, TTL, priority, verification status) with recommended labels and copy buttons.
    • New public GET /v1/domains/{id} and verify endpoint for single-domain retrieval and verification.
  • Improvements

    • Verification details exposed (last-checked time, verification errors) and normalized DKIM/SPF/DKIM status handling.
    • More robust DMARC lookups and consistent DNS record augmentation across domain views.
  • SDK & Docs

    • Added Domains clients and types to Python and JS SDKs; OpenAPI/docs updated with dnsRecords and verification fields.

@vercel
Copy link
Copy Markdown

vercel bot commented Sep 26, 2025

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

Project Deployment Preview Comments Updated (UTC)
unsend-marketing Ready Ready Preview Comment Sep 26, 2025 11:40pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Sep 26, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Replaces hard-coded DNS rows in the domain settings UI with dynamic rendering from domainQuery.data?.dnsRecords and updates component prop types to use enriched domain response types (RouterOutputs/DomainResponse/DomainWithDnsRecords). Adds DomainDnsRecordSchema and extends DomainSchema with verificationError, lastCheckedTime, and dnsRecords. Implements domain-service helpers (parseDomainStatus, buildDnsRecords, withDnsRecords) and updates server resolvers/public-api to delegate domain fetching/enrichment to that service (including a new GET /v1/domains/{id} public route). Updates OpenAPI/schema, docs, TypeScript SDK, and Python SDK to expose dnsRecords and new domain-related endpoints/types.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “feat: expose domain dns records via api” succinctly captures the primary change of exposing DNS records in the API responses, directly reflecting the core feature implemented across service, TRPC, public API, and UI layers. It is concise, specific, and aligns with the conventional commit style without extraneous detail.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 839e9b2 and 7e64142.

📒 Files selected for processing (2)
  • apps/web/src/server/public-api/api/domains/get-domain.ts (1 hunks)
  • apps/web/src/server/service/domain-service.ts (5 hunks)

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

❤️ Share

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

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Sep 26, 2025

Deploying usesend with  Cloudflare Pages  Cloudflare Pages

Latest commit: 839e9b2
Status: ✅  Deploy successful!
Preview URL: https://2333cfbf.usesend.pages.dev
Branch Preview URL: https://codex-fix-api-domain-creatio.usesend.pages.dev

View logs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (7)
apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx (4)

131-139: Ensure stable, unique keys for DNS rows

type-name can collide across records (e.g., multiple TXT with same name). Include value or index to guarantee uniqueness.

-const key = `${record.type}-${record.name}`;
+const key = `${record.type}-${record.name}-${record.value}`;

185-197: Add rollback on mutation error for click tracking

Currently optimistic UI flips the switch but doesn’t revert on error.

 function handleClickTrackingChange() {
   setClickTracking(!clickTracking);
   updateDomain.mutate(
     { id: domain.id, clickTracking: !clickTracking },
     {
       onSuccess: () => {
         utils.domain.invalidate();
         toast.success("Click tracking updated");
       },
+      onError: (err) => {
+        setClickTracking((v) => !v);
+        toast.error(err?.message ?? "Failed to update click tracking");
+      },
     },
   );
 }

198-209: Add rollback on mutation error for open tracking

Mirror error handling for openTracking.

 function handleOpenTrackingChange() {
   setOpenTracking(!openTracking);
   updateDomain.mutate(
     { id: domain.id, openTracking: !openTracking },
     {
       onSuccess: () => {
         utils.domain.invalidate();
         toast.success("Open tracking updated");
       },
+      onError: (err) => {
+        setOpenTracking((v) => !v);
+        toast.error(err?.message ?? "Failed to update open tracking");
+      },
     },
   );
 }

228-231: Brand consistency: “useSend”

Copy says “Unsend”; align with “useSend”.

-  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
apps/web/src/server/service/domain-service.ts (3)

235-239: Robust DMARC presence check (join TXT chunks, avoid partial access)

resolveTxt returns string[][]. Using [0][0] is brittle. Join chunks and check any record exists.

-const _dmarcRecord = await getDmarcRecord(tldts.getDomain(domain.name)!);
-const dmarcRecord = _dmarcRecord?.[0]?.[0];
+const orgDomain = tldts.getDomain(domain.name);
+const _dmarcRecord = orgDomain ? await getDmarcRecord(orgDomain) : null;
+const dmarcRecord = _dmarcRecord?.map(parts => parts.join("")).find(Boolean);

And use Boolean(dmarcRecord) where applicable (dmarcAdded, etc.).

Also applies to: 248-256


215-224: Normalize getDomain return shape consistently

When not verifying you return withDnsRecords(domain) without verificationError/lastCheckedTime normalization, but during verifying you include them. Prefer consistent shape to simplify consumers (TRPC/public API).

Option: always compute normalized fields (verificationError?: string | null, lastCheckedTime?: string | null) and include them (null when absent).

Also applies to: 258-278, 281-282


44-75: MX row status uses SPF status

If you intend independent status per record, consider mapping:

  • DKIM TXT → dkimStatus
  • SPF TXT → spfStatus
  • MX → Mail From status (same as spfStatus today), but verify with SES semantics.

No change required if intentional; flagging for clarity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10fc285 and b774bea.

📒 Files selected for processing (8)
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx (5 hunks)
  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx (1 hunks)
  • apps/web/src/lib/zod/domain-schema.ts (2 hunks)
  • apps/web/src/server/api/routers/domain.ts (2 hunks)
  • apps/web/src/server/public-api/api/domains/create-domain.ts (0 hunks)
  • apps/web/src/server/public-api/api/domains/get-domains.ts (2 hunks)
  • apps/web/src/server/service/domain-service.ts (4 hunks)
  • apps/web/src/types/domain.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/src/server/public-api/api/domains/create-domain.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{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/[domainId]/send-test-mail.tsx
  • apps/web/src/lib/zod/domain-schema.ts
  • apps/web/src/server/api/routers/domain.ts
  • apps/web/src/types/domain.ts
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
  • apps/web/src/server/public-api/api/domains/get-domains.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript with 2-space indentation and semicolons (enforced by Prettier)
ESLint must pass with zero warnings using @usesend/eslint-config
Do not use dynamic imports (avoid import() and dynamic loading)

Files:

  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx
  • apps/web/src/lib/zod/domain-schema.ts
  • apps/web/src/server/api/routers/domain.ts
  • apps/web/src/types/domain.ts
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
  • apps/web/src/server/public-api/api/domains/get-domains.ts
**/*.{ts,tsx,md}

📄 CodeRabbit inference engine (AGENTS.md)

Format code and docs with Prettier 3

Files:

  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx
  • apps/web/src/lib/zod/domain-schema.ts
  • apps/web/src/server/api/routers/domain.ts
  • apps/web/src/types/domain.ts
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
  • apps/web/src/server/public-api/api/domains/get-domains.ts
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/**/*.{ts,tsx}: In apps/web, use the "/" alias for src imports (e.g., import { x } from "/utils/x")
Prefer using tRPC for API calls unless explicitly instructed otherwise

Files:

  • apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx
  • apps/web/src/lib/zod/domain-schema.ts
  • apps/web/src/server/api/routers/domain.ts
  • apps/web/src/types/domain.ts
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx
  • apps/web/src/server/public-api/api/domains/get-domains.ts
🧬 Code graph analysis (5)
apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx (1)
apps/web/src/types/domain.ts (1)
  • DomainWithDnsRecords (13-17)
apps/web/src/server/api/routers/domain.ts (1)
apps/web/src/server/service/domain-service.ts (1)
  • getDomains (318-333)
apps/web/src/types/domain.ts (1)
packages/python-sdk/usesend/types.py (1)
  • Domain (26-42)
apps/web/src/server/service/domain-service.ts (2)
packages/python-sdk/usesend/types.py (1)
  • Domain (26-42)
apps/web/src/types/domain.ts (1)
  • DomainDnsRecord (3-11)
apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx (2)
apps/web/src/server/api/root.ts (1)
  • AppRouter (40-40)
packages/ui/src/text-with-copy.tsx (1)
  • TextWithCopyButton (6-41)
🔇 Additional comments (6)
apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx (2)

36-43: Verify Next.js/React support for use(params) in client page

Using use(params: Promise<...>) in a client component relies on React 19 + Next 15 pattern. If you’re on Next 14 or not using React 19, switch to useParams().

Example alternative:

-export default function DomainItemPage({ params }: { params: Promise<{ domainId: string }> }) {
-  const { domainId } = use(params);
+import { useParams } from "next/navigation";
+export default function DomainItemPage() {
+  const { domainId } = useParams<{ domainId: string }>();

4-4: No Prisma enums in client component
apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx no longer imports DomainStatus from @prisma/client; no changes needed.

Likely an incorrect or invalid review comment.

apps/web/src/server/service/domain-service.ts (1)

33-76: Likely incorrect DNS host composition for subdomains; prefer FQDNs

Using subdomainSuffix = .${domain.subdomain} yields names like mail.news for a domain “news.example.com”, which is not an FQDN and can mislead users (and may be wrong relative to the hosted zone). Recommend emitting FQDNs:

  • MX/TXT (SPF) at mail.
  • DKIM at ._domainkey.
  • DMARC at _dmarc.

Apply:

-function buildDnsRecords(domain: Domain): DomainDnsRecord[] {
-  const subdomainSuffix = domain.subdomain ? `.${domain.subdomain}` : "";
-  const mailDomain = `mail${subdomainSuffix}`;
-  const dkimSelector = domain.dkimSelector ?? "usesend";
+function buildDnsRecords(domain: Domain): DomainDnsRecord[] {
+  const fullDomain = domain.name; // e.g., "news.example.com" or "example.com"
+  const orgDomain = tldts.getDomain(domain.name) ?? domain.name; // e.g., "example.com"
+  const mailHost = `mail.${fullDomain}`;
+  const dkimSelector = domain.dkimSelector ?? "usesend";
 
   const spfStatus = parseDomainStatus(domain.spfDetails);
   const dkimStatus = parseDomainStatus(domain.dkimStatus);
   const dmarcStatus = domain.dmarcAdded
     ? DomainStatus.SUCCESS
     : DomainStatus.NOT_STARTED;
 
   return [
     {
       type: "MX",
-      name: mailDomain,
+      name: mailHost,
       value: `feedback-smtp.${domain.region}.amazonses.com`,
       ttl: "Auto",
       priority: "10",
       status: spfStatus,
     },
     {
       type: "TXT",
-      name: `${dkimSelector}._domainkey${subdomainSuffix}`,
+      name: `${dkimSelector}._domainkey.${fullDomain}`,
       value: `p=${domain.publicKey}`,
       ttl: "Auto",
       status: dkimStatus,
     },
     {
       type: "TXT",
-      name: mailDomain,
+      name: mailHost,
       value: "v=spf1 include:amazonses.com ~all",
       ttl: "Auto",
       status: spfStatus,
     },
     {
       type: "TXT",
-      name: "_dmarc",
+      name: `_dmarc.${orgDomain}`,
       value: "v=DMARC1; p=none;",
       ttl: "Auto",
       status: dmarcStatus,
       recommended: true,
     },
   ];
 }

Please confirm whether your UI expects FQDNs or relative names; adjust “name” accordingly to match customer DNS provider UX.

apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx (1)

8-13: Align props with DNS-enriched domain shape

Using DomainWithDnsRecords keeps this component in sync with the expanded domain payload from the service layer while preserving access to the base Prisma fields such as id. Looks good.

apps/web/src/server/api/routers/domain.ts (1)

45-46: Centralize domain fetching through the service

Routing the list query through getDomains ensures the TRPC response picks up the DNS records and normalized metadata produced by withDnsRecords, matching the new contract everywhere we expose domains. 👍

apps/web/src/server/public-api/api/domains/get-domains.ts (1)

26-28: Reuse service logic for public API domains

Delegating to getDomainsService gives the public API the same DNS-enriched shape and optional domain scoping that the internal routes already enjoy, avoiding divergence in the serialization path. Nice consolidation.

Comment thread apps/web/src/lib/zod/domain-schema.ts
Comment thread apps/web/src/types/domain.ts
@KMKoushik
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
packages/python-sdk/usesend/domains.py (1)

1-38: Remove the unused noqa and move the type-only import

Ruff flags # noqa: E402 as unused (RUF100). Import UseSend inside a TYPE_CHECKING block instead and drop the suppression so linting passes. Based on static analysis hints.

-from typing import Optional, Tuple, List
+from typing import TYPE_CHECKING, Optional, Tuple, List
@@
-class Domains:
+if TYPE_CHECKING:
+    from .usesend import UseSend
+
+
+class Domains:
@@
-    def __init__(self, usesend: "UseSend") -> None:
+    def __init__(self, usesend: "UseSend") -> None:
         self.usesend = usesend
@@
-from .usesend import UseSend  # noqa: E402  pylint: disable=wrong-import-position
🧹 Nitpick comments (1)
apps/web/src/server/service/domain-service.ts (1)

13-27: Consider using a Map for better performance

The Set lookup for domain status validation is functionally correct, but a Map or direct enum comparison would be more explicit about the intended behavior.

-const DOMAIN_STATUS_VALUES = new Set(Object.values(DomainStatus));
-
-function parseDomainStatus(status?: string | null): DomainStatus {
-  if (!status) {
-    return DomainStatus.NOT_STARTED;
-  }
-
-  const normalized = status.toUpperCase();
-
-  if (DOMAIN_STATUS_VALUES.has(normalized as DomainStatus)) {
-    return normalized as DomainStatus;
-  }
-
-  return DomainStatus.NOT_STARTED;
-}
+function parseDomainStatus(status?: string | null): DomainStatus {
+  if (!status) {
+    return DomainStatus.NOT_STARTED;
+  }
+
+  const normalized = status.toUpperCase();
+
+  if (Object.values(DomainStatus).includes(normalized as DomainStatus)) {
+    return normalized as DomainStatus;
+  }
+
+  return DomainStatus.NOT_STARTED;
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b774bea and 839e9b2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • apps/docs/api-reference/domains/get-domain.mdx (1 hunks)
  • apps/docs/api-reference/domains/list-domains.mdx (1 hunks)
  • apps/docs/api-reference/openapi.json (12 hunks)
  • apps/docs/docs.json (4 hunks)
  • apps/web/src/server/api/routers/domain.ts (4 hunks)
  • apps/web/src/server/public-api/api/domains/get-domain.ts (1 hunks)
  • apps/web/src/server/public-api/index.ts (2 hunks)
  • apps/web/src/server/service/domain-service.ts (5 hunks)
  • package.json (2 hunks)
  • packages/python-sdk/pyproject.toml (1 hunks)
  • packages/python-sdk/usesend/__init__.py (1 hunks)
  • packages/python-sdk/usesend/domains.py (1 hunks)
  • packages/python-sdk/usesend/types.py (3 hunks)
  • packages/python-sdk/usesend/usesend.py (2 hunks)
  • packages/sdk/package.json (1 hunks)
  • packages/sdk/src/domain.ts (2 hunks)
  • packages/sdk/src/email.ts (1 hunks)
  • packages/sdk/types/schema.d.ts (8 hunks)
✅ Files skipped from review due to trivial changes (4)
  • packages/sdk/package.json
  • packages/python-sdk/pyproject.toml
  • packages/sdk/src/email.ts
  • apps/docs/api-reference/domains/list-domains.mdx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{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/public-api/api/domains/get-domain.ts
  • packages/sdk/src/domain.ts
  • apps/web/src/server/public-api/index.ts
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/server/api/routers/domain.ts
  • packages/sdk/types/schema.d.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript with 2-space indentation and semicolons (enforced by Prettier)
ESLint must pass with zero warnings using @usesend/eslint-config
Do not use dynamic imports (avoid import() and dynamic loading)

Files:

  • apps/web/src/server/public-api/api/domains/get-domain.ts
  • packages/sdk/src/domain.ts
  • apps/web/src/server/public-api/index.ts
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/server/api/routers/domain.ts
  • packages/sdk/types/schema.d.ts
**/*.{ts,tsx,md}

📄 CodeRabbit inference engine (AGENTS.md)

Format code and docs with Prettier 3

Files:

  • apps/web/src/server/public-api/api/domains/get-domain.ts
  • packages/sdk/src/domain.ts
  • apps/web/src/server/public-api/index.ts
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/server/api/routers/domain.ts
  • packages/sdk/types/schema.d.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/**/*.{ts,tsx}: In apps/web, use the "/" alias for src imports (e.g., import { x } from "/utils/x")
Prefer using tRPC for API calls unless explicitly instructed otherwise

Files:

  • apps/web/src/server/public-api/api/domains/get-domain.ts
  • apps/web/src/server/public-api/index.ts
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/server/api/routers/domain.ts
🧬 Code graph analysis (8)
apps/web/src/server/public-api/api/domains/get-domain.ts (4)
apps/web/src/lib/zod/domain-schema.ts (1)
  • DomainSchema (34-56)
apps/web/src/server/service/domain-service.ts (1)
  • getDomain (210-279)
apps/web/src/server/public-api/hono.ts (1)
  • PublicAPIApp (136-136)
apps/web/src/server/public-api/api-error.ts (1)
  • UnsendApiError (62-75)
packages/python-sdk/usesend/usesend.py (2)
packages/python-sdk/usesend/domains.py (1)
  • Domains (15-35)
packages/sdk/src/domain.ts (1)
  • Domains (40-73)
packages/python-sdk/usesend/domains.py (2)
packages/python-sdk/usesend/types.py (5)
  • APIError (335-337)
  • Domain (41-60)
  • DomainCreate (66-68)
  • DomainCreateResponse (71-90)
  • DomainVerifyResponse (93-94)
packages/python-sdk/usesend/usesend.py (4)
  • get (109-110)
  • post (106-107)
  • put (112-113)
  • UseSend (34-121)
packages/sdk/src/domain.ts (2)
packages/sdk/types/index.ts (1)
  • ErrorResponse (1-4)
packages/sdk/types/schema.d.ts (1)
  • paths (6-907)
apps/web/src/server/public-api/index.ts (1)
apps/web/src/server/service/domain-service.ts (1)
  • getDomain (210-279)
packages/python-sdk/usesend/__init__.py (1)
packages/python-sdk/usesend/domains.py (1)
  • Domains (15-35)
apps/web/src/server/service/domain-service.ts (2)
packages/python-sdk/usesend/types.py (1)
  • Domain (41-60)
apps/web/src/types/domain.ts (1)
  • DomainDnsRecord (3-11)
apps/web/src/server/api/routers/domain.ts (2)
apps/web/src/server/service/domain-service.ts (2)
  • getDomains (315-330)
  • getDomain (210-279)
apps/web/src/server/api/trpc.ts (1)
  • domainProcedure (165-176)
🪛 Ruff (0.13.1)
packages/python-sdk/usesend/usesend.py

127-127: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)

packages/python-sdk/usesend/domains.py

37-37: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)

packages/python-sdk/usesend/__init__.py

7-7: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (13)
package.json (1)

34-34: LGTM: Standard Mintlify dependency update

The bump from 4.0.510 to 4.2.128 appears to be a standard maintenance update. Updates to the most recent version of the Mintlify CLI typically include improvements and bug fixes for documentation tooling.

apps/docs/api-reference/domains/get-domain.mdx (1)

2-2: LGTM: OpenAPI path correctly updated for single domain retrieval

The path change from "GET /v1/domains" to "GET /v1/domains/{id}" accurately reflects the new endpoint for retrieving a specific domain by ID, aligning with the broader PR changes that introduce domain-by-id functionality.

apps/web/src/server/service/domain-service.ts (5)

29-72: LGTM: DNS record construction logic is well-structured

The buildDnsRecords function correctly constructs DNS records with proper subdomain handling, status parsing, and includes all required record types (MX, TXT for SPF/DKIM, and DMARC).


74-81: LGTM: Clean domain augmentation pattern

The withDnsRecords helper follows a clean pattern for augmenting domains with DNS record data while preserving type safety.


202-207: LGTM: Proper initialization with DNS records

The createDomain function correctly initializes the new domain status fields and applies DNS record augmentation to the returned domain.


210-279: LGTM: Comprehensive domain verification handling

The getDomain function properly handles both static and verifying domains, with appropriate normalization and augmentation. The verification flow correctly updates domain status and includes all necessary fields in the response.


315-330: LGTM: Consistent domain augmentation

The getDomains function maintains consistency by applying the same DNS record augmentation to all returned domains, with proper filtering support.

apps/web/src/server/api/routers/domain.ts (1)

14-14: LGTM: Clean migration to domain service layer

The changes properly delegate domain retrieval to the domain service layer, ensuring consistent data augmentation with DNS records across all domain endpoints. The import addition and function calls are correctly implemented.

Also applies to: 44-46, 48-50

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

105-171: LGTM: Comprehensive DNS records schema addition

The DNS records schema is well-defined with proper field types, enums, and required fields. The schema correctly includes:

  • DNS record types (MX, TXT)
  • All necessary fields (type, name, value, ttl, priority, status, recommended)
  • Proper nullable handling for priority field
  • Status enum matching domain verification states

Also applies to: 291-357, 540-606


27-27: LGTM: Accurate endpoint descriptions

The endpoint descriptions have been updated to properly reflect their functionality:

  • GET /v1/domains now describes retrieving domains accessible by API key
  • PUT /v1/domains/{id}/verify correctly describes domain verification
  • GET /v1/domains/{id} properly describes single domain retrieval

Also applies to: 392-392, 464-464


409-445: LGTM: Proper error response documentation

The addition of 403 and 404 error responses for the domain verification endpoint provides clear API contract documentation for error handling scenarios.


180-181: LGTM: DNS records properly marked as required

The addition of dnsRecords to the required fields array ensures API consumers can expect this field to always be present in domain responses.

Also applies to: 366-367, 615-616


748-750: LGTM: Email status enum extension

The addition of "CANCELLED" and "SUPPRESSED" to the email status enums properly extends the email event tracking capabilities.

Also applies to: 1012-1014

Comment on lines +45 to +55
let enriched;
try {
enriched = await getDomainService(id, team.id);
} catch (e) {
throw new UnsendApiError({
code: "NOT_FOUND",
message: "Domain not found",
});
}

return c.json(enriched);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve non-404 failures from the domain service

Catching every exception from getDomainService and returning NOT_FOUND hides legitimate server failures (e.g., AWS SES/network/DB issues inside withDnsRecords). This misleads clients, complicates debugging, and masks outages as missing resources. Only translate the explicit “Domain not found” sentinel to 404; anything else should surface as an internal error.

-    let enriched;
-    try {
-      enriched = await getDomainService(id, team.id);
-    } catch (e) {
-      throw new UnsendApiError({
-        code: "NOT_FOUND",
-        message: "Domain not found",
-      });
-    }
-
-    return c.json(enriched);
+    try {
+      const enriched = await getDomainService(id, team.id);
+      return c.json(enriched);
+    } catch (error) {
+      if (error instanceof Error && error.message === "Domain not found") {
+        throw new UnsendApiError({
+          code: "NOT_FOUND",
+          message: "Domain not found",
+        });
+      }
+
+      throw new UnsendApiError({
+        code: "INTERNAL_SERVER_ERROR",
+        message: "Failed to retrieve domain",
+      });
+    }
📝 Committable suggestion

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

Suggested change
let enriched;
try {
enriched = await getDomainService(id, team.id);
} catch (e) {
throw new UnsendApiError({
code: "NOT_FOUND",
message: "Domain not found",
});
}
return c.json(enriched);
try {
const enriched = await getDomainService(id, team.id);
return c.json(enriched);
} catch (error) {
if (error instanceof Error && error.message === "Domain not found") {
throw new UnsendApiError({
code: "NOT_FOUND",
message: "Domain not found",
});
}
throw new UnsendApiError({
code: "INTERNAL_SERVER_ERROR",
message: "Failed to retrieve domain",
});
}

from . import types

__all__ = ["UseSend", "UseSendHTTPError", "types"]
__all__ = ["UseSend", "UseSendHTTPError", "types", "Domains"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Keep __all__ sorted to satisfy Ruff.

Ruff (RUF022) enforces a sorted __all__. Reordering the exports alphabetically will clear the lint failure.
(Based on static analysis hints)

-__all__ = ["UseSend", "UseSendHTTPError", "types", "Domains"]
+__all__ = ["Domains", "UseSend", "UseSendHTTPError", "types"]
📝 Committable suggestion

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

Suggested change
__all__ = ["UseSend", "UseSendHTTPError", "types", "Domains"]
__all__ = ["Domains", "UseSend", "UseSendHTTPError", "types"]
🧰 Tools
🪛 Ruff (0.13.1)

7-7: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

🤖 Prompt for AI Agents
In packages/python-sdk/usesend/__init__.py around line 7, the __all__ list is
not alphabetically sorted (RUF022); reorder the exported names in
ASCII/alphabetical order so that the list is sorted (e.g., put "Domains" first,
then "UseSend", then "UseSendHTTPError", then "types") to satisfy Ruff.

Comment on lines +25 to +38
DNSRecordType = Literal['MX', 'TXT']


class DNSRecord(TypedDict, total=False):
type: DNSRecordType
name: str
value: str
ttl: str
priority: Optional[str]
status: DomainStatus
recommended: Optional[bool]


DNSRecords = List[DNSRecord]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Include CNAME in DNSRecordType.

buildDnsRecords surfaces DKIM CNAME entries from SES, so the API will return records with type: "CNAME". Constraining the Literal to "MX"/"TXT" makes the SDK types inaccurate and will trip type-checkers once those records show up. Please add "CNAME" to the Literal.

-DNSRecordType = Literal['MX', 'TXT']
+DNSRecordType = Literal['CNAME', 'MX', 'TXT']
📝 Committable suggestion

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

Suggested change
DNSRecordType = Literal['MX', 'TXT']
class DNSRecord(TypedDict, total=False):
type: DNSRecordType
name: str
value: str
ttl: str
priority: Optional[str]
status: DomainStatus
recommended: Optional[bool]
DNSRecords = List[DNSRecord]
DNSRecordType = Literal['CNAME', 'MX', 'TXT']
class DNSRecord(TypedDict, total=False):
type: DNSRecordType
name: str
value: str
ttl: str
priority: Optional[str]
status: DomainStatus
recommended: Optional[bool]
DNSRecords = List[DNSRecord]
🤖 Prompt for AI Agents
In packages/python-sdk/usesend/types.py around lines 25 to 38, the DNSRecordType
Literal currently only allows 'MX' and 'TXT' but SES can return DKIM CNAME
entries; update the DNSRecordType definition to include 'CNAME' so the TypedDict
accurately represents API responses and avoids type-checker failures.

# Import here to avoid circular dependency during type checking
from .emails import Emails # noqa: E402 pylint: disable=wrong-import-position
from .contacts import Contacts # noqa: E402 pylint: disable=wrong-import-position
from .domains import Domains # type: ignore # noqa: E402
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Drop the unused noqa directive.

Ruff (RUF100) reports # noqa: E402 as unused because that rule isn’t enabled; trimming it keeps the lint run clean.
(Based on static analysis hints)

-from .domains import Domains  # type: ignore  # noqa: E402
+from .domains import Domains  # type: ignore
📝 Committable suggestion

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

Suggested change
from .domains import Domains # type: ignore # noqa: E402
from .domains import Domains # type: ignore
🧰 Tools
🪛 Ruff (0.13.1)

127-127: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)

🤖 Prompt for AI Agents
In packages/python-sdk/usesend/usesend.py around line 127, the import line
includes an unused comment "# noqa: E402"; remove that directive so the line
reads simply "from .domains import Domains" to satisfy Ruff (RUF100) and keep
lint output clean.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting

@KMKoushik KMKoushik merged commit 76fdad6 into main Sep 26, 2025
2 of 4 checks passed
@KMKoushik KMKoushik deleted the codex/fix-api-domain-creation-issue branch September 26, 2025 23:40
@coderabbitai coderabbitai bot mentioned this pull request Nov 28, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jan 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant