Skip to content

Feature resender receipt - #393

Merged
BleedingDev merged 6 commits into
masterfrom
feature-resender-receipt
May 12, 2026
Merged

Feature resender receipt#393
BleedingDev merged 6 commits into
masterfrom
feature-resender-receipt

Conversation

@tomas-cm1

@tomas-cm1 tomas-cm1 commented May 7, 2026

Copy link
Copy Markdown
Collaborator

Tahle branchka je vedená nad feature-resender. Nejdřív se musí zmergovat teda feature-resender

Summary by CodeRabbit

  • New Features

    • Automatic order receipt PDFs generated and emailed when orders are placed
    • Health check API endpoint added
  • Improvements

    • Email sending refactored to use typed template definitions with improved attachment and timeout handling
    • Admin email logs now surface related order IDs; payment‑reminder job avoids duplicate reminders
  • Removals

    • Legacy React-based email templates removed
  • Chores

    • Backend configuration updated to support notification providers and new modules
  • Migrations

    • Email log schema extended to include optional order ID

@semanticdiff-com

semanticdiff-com Bot commented May 7, 2026

Copy link
Copy Markdown

@vercel

vercel Bot commented May 7, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
new-engine-ui-storybook Ready Ready Preview, Comment May 12, 2026 8:42am

@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds PDF order-receipt generation and delivery, Resend template-driven email sending with attachment and timeout support, records order_id on email logs (model/migration/API/UI), prevents duplicate payment reminders, refactors subject/template handling, wires an order.placed workflow and subscriber, updates Medusa config, and adds a frontend health endpoint.

Changes

Order Receipt Email System

Layer / File(s) Summary
Receipt domain types & helpers
apps/medusa-be/src/modules/order-receipt/helpers.ts
Defines receipt domain types and implements money coercion, ASCII/diacritic normalisation, PDF escaping, date/currency formatting, item/order accessors, subtotal/tax/total calculations, truncation, width estimation, and PDF command generation.
Order receipt module & PDF service
apps/medusa-be/src/modules/order-receipt/index.ts, apps/medusa-be/src/modules/order-receipt/service.ts
Module entrypoint and service that builds standalone PDF receipts (layout, items table, totals), assembles minimal PDF structure and exposes generateOrderReceiptAttachment.
Resend template definitions
apps/medusa-be/src/modules/resend/templates.ts
Adds typed template definitions, ORDER_PLACED key, and helpers getResendTemplateDefinition() / getResendTemplateSubject().
Resend provider: template-driven sending
apps/medusa-be/src/modules/resend/service.ts
Refactors sending to use template definitions, adds attachment normalisation, variable validation, request timeout support, and fetch-based Resend API calls with normalized error handling.
Removed React email components
apps/medusa-be/src/modules/resend/emails/forgot-password.tsx, apps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsx
Deletes React email components replaced by template-definition approach.
send-notification: subject & order_id logging
apps/medusa-be/src/workflows/steps/send-notification.ts, apps/medusa-be/src/utils/order-email-templates.ts
Derives subjects from template definitions and records computed order_id on created email logs; ORDER_PAYMENT_REMINDER subject now uses template subject helper.
Email log model, migration, API & admin UI
apps/medusa-be/src/modules/email-log/migrations/Migration20260508114500.ts, apps/medusa-be/src/modules/email-log/models/email-log.ts, apps/medusa-be/src/api/admin/email-logs/*, apps/medusa-be/src/admin/routes/emails/page.tsx
Adds nullable order_id column and partial index with migration; API DTOs and admin UI updated to include and display order_id.
Order receipt workflow and subscriber
apps/medusa-be/src/workflows/send-order-receipt.ts, apps/medusa-be/src/subscribers/order-placed.ts
Workflow step fetches order, generates PDF attachment and creates notification using order-placed template; subscriber triggers workflow on order.placed.
Payment reminder job & utilities
apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts, apps/medusa-be/src/utils/order-payment-reminders.ts
Adds created_at to reminder order type, a 24h min-age constant and isPaymentReminderReadyOrder; job deduplicates reminders by querying email logs and skips already-reminded orders.
Medusa config: notification provider & modules
apps/medusa-be/medusa-config.ts
Computes admin allowed hosts, selects notification provider (local or resend) using env vars, wires resend provider to ./src/modules/resend, and registers email-log and order-receipt modules.
Frontend health endpoint
apps/frontend-demo/src/app/api/health/route.ts
Adds Next.js API route GET returning { status: "ok" } JSON response.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • TechsioCZ/new-engine#375: Modifies overlapping Medusa notification subsystem and Resend email infrastructure, email workflows, and module registration patterns.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feature resender receipt' is vague and does not clearly describe the main changes; it uses generic terminology without specifying the actual feature being implemented. Consider a more descriptive title such as 'Add order receipt PDF generation and email workflow' or 'Implement automated order receipt emails via Resend' to clearly convey the primary change.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature-resender-receipt
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feature-resender-receipt

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 32

Caution

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

⚠️ Outside diff range comments (1)
apps/medusa-be/package.json (1)

66-75: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

refactor: migrate @react-email/components to unified react-email package

@react-email/components is deprecated as of react-email v6.0.0 (April 2026). The correct approach is not to upgrade to v1.0, but to migrate to the unified react-email package:

  • Uninstall: @react-email/components, @react-email/render, and individual component packages
  • Install: react-email@latest
  • Update imports: from @react-email/components to react-email

Regarding resend: upgrading from ^4.7.0 to ^6.12.2 is low-impact, with minimal breaking changes (only inlineContentIdcontentId in attachment schema, plus @react-email/render becoming an optional peer dependency).

Note: No source code usage of either package was found in the codebase. Verify that these dependencies are actually needed or remove them if unused.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/package.json` around lines 66 - 75, The package
`@react-email/components` is deprecated—replace it by removing
`@react-email/components` (and any `@react-email/render` or individual
`@react-email/`* component packages) and installing react-email@latest, then
update all imports from "@react-email/components" to "react-email" (search for
import sites referencing `@react-email/`*); also review the resend entry and, if
upgrading to ^6.12.2, update any attachment schema usage from inlineContentId to
contentId and add `@react-email/render` as an optional peer only if needed;
finally verify whether `@react-email/components` and resend are actually used
anywhere and remove them from package.json if unused.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/medusa-be/.env.template`:
- Around line 25-31: Move the Resend signing secret out of the CORS/backend
block: remove RESEND_WEBHOOK_SECRET from alongside
STOREFRONT_URL/STORE_CORS/ADMIN_CORS/AUTH_CORS/MEDUSA_BACKEND_URL and add it to
a new dedicated "Email / Webhook" section (or alongside other Resend env vars
like RESEND_API_KEY, RESEND_FROM_ADDRESS if present) with a clear comment header
indicating it's the Resend webhook signing secret; update the .env.template so
RESEND_WEBHOOK_SECRET sits with related Resend/email vars for clarity.

In `@apps/medusa-be/medusa-config.ts`:
- Around line 14-15: The current MEDUSA_ADMIN_ALLOWED_HOSTS sets a bare string
or undefined which Vite's server.allowedHosts rejects; update the assignment for
MEDUSA_ADMIN_ALLOWED_HOSTS so that when process.env.NODE_ENV === "development"
it stays true, otherwise if process.env.MEDUSA_BACKEND_URL is set wrap it in an
array (e.g., [process.env.MEDUSA_BACKEND_URL]) and if unset provide an empty
array to satisfy the string[] | true type expected by Vite; modify the constant
defined as MEDUSA_ADMIN_ALLOWED_HOSTS to implement this logic and ensure
server.allowedHosts receives the correct type.

In `@apps/medusa-be/src/admin/routes/emails/page.tsx`:
- Around line 209-212: The nullish fallback is dead because formatRecipient(...)
always returns a string; remove the unreachable ?? branch and pass the formatted
value directly to DetailField. Replace value={formatRecipient(resendEmail?.to)
?? detail.email_log.sent_to} with value={formatRecipient(resendEmail?.to)} (or,
if you intended to prefer detail.email_log.sent_to when formatRecipient yields
"-" use a simple conditional: formatRecipient(resendEmail?.to) === "-" ?
detail.email_log.sent_to : formatRecipient(resendEmail?.to)). This touches the
DetailField value prop and uses formatRecipient and detail.email_log.sent_to to
locate the change.

In `@apps/medusa-be/src/api/admin/email-logs/`[id]/route.ts:
- Around line 59-64: The outbound fetch to Resend (the call assigning `response
= await fetch(`${RESEND_EMAILS_API}/${emailId}`, ...)`) needs an
AbortController-based timeout to avoid hanging; create an AbortController, start
a setTimeout to call controller.abort() after ~10_000 ms, pass controller.signal
into the fetch options, and clear the timeout after the fetch completes (or in a
finally block). Ensure the fetch error handling treats an aborted request
correctly (the thrown error will be an AbortError) and keep existing headers
(`Authorization`/`Content-Type`) when adding the `signal` option.
- Around line 19-21: The local EmailLogService type augments
EmailLogModuleService with retrieveEmailLog causing an unsafe cast when calling
resolve<EmailLogService>(EMAIL_LOG_MODULE); fix by removing the unvalidated
augmentation and either (A) add a real retrieveEmailLog method to the actual
EmailLogModuleService implementation and its exported type/signature so resolve
returns a correctly typed service, or (B) keep the concrete service type and use
a runtime type guard before invoking retrieveEmailLog (check typeof
service.retrieveEmailLog === "function" or instanceof check) and handle the
missing-method case instead of casting; reference EmailLogService,
EmailLogModuleService, retrieveEmailLog, resolve and EMAIL_LOG_MODULE when
making the change.

In `@apps/medusa-be/src/api/admin/email-logs/route.ts`:
- Around line 41-58: Normalize and validate the parsed pagination values before
using and returning them: parse and coerce req.query.limit and req.query.offset
into integers (e.g., using parseInt or Number with isFinite checks), provide
safe defaults when parsing fails (limit -> 20, offset -> 0), cap limit to a
reasonable maximum (e.g., MAX_LIMIT = 100) to avoid large DB scans, and then
pass the normalized skip/take into emailLogService.listAndCountEmailLogs and
return those same normalized values in the JSON response (email_logs, count,
limit, offset) so the echoed pagination matches the actual query behavior;
update the variables named limit and offset and the call to
emailLogService.listAndCountEmailLogs and the response mapping that uses
toEmailLogResponse accordingly.

In `@apps/medusa-be/src/api/admin/orders/`[id]/email/route.ts:
- Around line 28-30: Remove the redundant runtime guard that throws when `!id`
in the dynamic route handler in route.ts—`req.params.id` on the `[id]` route is
guaranteed by the router, so delete the check and associated MedusaError throw
that references `id`; ensure any downstream code still uses the `id` variable
and add no-op comment only if you want to document the router guarantee.

In `@apps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.ts`:
- Around line 11-12: The current parsing of limit (const limit and
normalizedLimit) uses Number.isFinite which permits 0, negatives and accepts
empty string; change the logic to parse req.query.limit as an integer (e.g.,
parseInt) and validate it is a positive integer > 0, apply a sane upper bound
(e.g., MAX_LIMIT) to prevent unbounded queries, and fall back to the default 5
when the value is NaN, <= 0, or exceeds the max; update normalizedLimit to use
this validated/clamped value.

In `@apps/medusa-be/src/api/webhooks/resend/route.ts`:
- Around line 183-186: The code currently sets webhookSecret using a fallback to
a deployment-specific variable (DC_N1_MEDUSA_RESEND_WEBHOOK_SECRET); remove that
fallback so webhookSecret is derived only from
process.env.RESEND_WEBHOOK_SECRET. Update any related runtime/docs
(.env.example) if you need to document a migration alias, but do not reference
or use DC_N1_MEDUSA_RESEND_WEBHOOK_SECRET in the route.ts code (look for the
webhookSecret assignment).
- Around line 120-126: The parsePayload function currently casts body straight
to ResendWebhookEvent without validating; change it to first treat body as
unknown (e.g., const candidate: unknown = body), validate that candidate is an
object and has the expected fields (the same runtime guard used later like
event.type and emailId or a small shape-check), and only then cast/return
candidate as ResendWebhookEvent; keep the fallback JSON.parse(payload) path the
same. Ensure you update the parsePayload function name and use the validated
intermediate variable so there is no direct unchecked "as ResendWebhookEvent"
cast.
- Around line 183-202: The route currently skips signature verification when
webhookSecret is missing (webhookSecret, verifySvixSignature), leaving the
endpoint unauthenticated; change this to "fail closed" by rejecting requests
when neither RESEND_WEBHOOK_SECRET nor DC_N1_MEDUSA_RESEND_WEBHOOK_SECRET is
set: in the resend route (route.ts) check for webhookSecret early and throw a
MedusaError (MedusaError.Types.INVALID_DATA or a more specific type) if absent,
or alternatively allow an explicit DEV_UNSAFE_ALLOW_UNAUTH env flag to opt-in
only in development — ensure the check is performed before any state mutations
and include a clear error message about missing webhook secret.

In `@apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts`:
- Around line 56-67: The sentCount is incremented for every unpaidOrders
iteration even when sendReminder returns early for orders with no email,
inflating the "sent X reminders" log; update the loop that iterates unpaidOrders
to only increment sentCount when sendReminder actually sent an email (e.g., have
sendReminder return a boolean or explicit result indicating success and check
that before doing sentCount += 1), or alternatively check order.email before
calling/incrementing so sentCount reflects only dispatched emails; modify the
for-loop around sendReminder/unpaidOrders and adjust error handling in the same
block (logger) accordingly.

In `@apps/medusa-be/src/modules/email-log/models/email-log.ts`:
- Around line 3-30: Add a soft-delete-safe unique constraint for the external
Resend identifier by updating the EmailLog model (the model.define("email_log",
...) / EmailLog) to include a unique index on "email_id" (e.g., name it
"UQ_email_log_email_id") with on: ["email_id"], unique: true and where:
"deleted_at IS NULL" so there can only be one non-deleted EmailLog per email_id
and upserts/webhook deduping behave reliably.

In `@apps/medusa-be/src/modules/order-receipt/helpers.ts`:
- Line 202: The line computing normalizedCurrency uses the logical OR operator
with currency (const normalizedCurrency = (currency || "CZK").toUpperCase()), so
change it to use the nullish coalescing operator: replace the || fallback with
?? to default only when currency is null or undefined (i.e., const
normalizedCurrency = (currency ?? "CZK").toUpperCase()); if you also need to
guard against empty string explicitly, add an explicit check for currency === ""
before applying the default.
- Around line 310-322: The getTotal function currently uses
Math.max(toNumber(order.total), fallbackTotal) which incorrectly overrides a
legitimate zero total; update the logic in getTotal to trust order.total when it
is explicitly present by returning toNumber(order.total) if order.total !== null
&& order.total !== undefined, and only use fallbackTotal when order.total is
absent; if the original intent was to treat zero as missing, replace the guard
with an explicit check like toNumber(order.total) > 0 and document that
assumption.

In `@apps/medusa-be/src/modules/order-receipt/service.ts`:
- Around line 112-113: The code currently creates visibleItems by slicing
order.items with slice(0, 12) which silently hides items beyond 12 and causes
the displayed line-item table to not match order totals; update the
order-receipt generation to handle truncation explicitly by either (a) rendering
all items across pages/layout changes, (b) appending a visible note like "and N
more items…" under the items table when visibleItems.length < (order.items ??
[]).length, or at minimum (c) emit a warning log when truncation occurs; locate
the slice(0, 12) usage (visibleItems) in the order-receipt service and implement
one of these fixes so customers see accurate context or are warned when items
are truncated.
- Line 61: Replace the use of logical OR and the hardcoded fallback when
creating supplierName: change the expression that reads const supplierName =
process.env.STORE_NAME || "N1 Shop" to use nullish coalescing (??) and supply
the fallback from configuration instead of a literal; for example, read a
well-named env/config value or accept a supplierName (or options object) via the
OrderReceiptModuleService constructor and use that injected value as the
fallback (i.e., const supplierName = process.env.STORE_NAME ??
injectedConfig.supplierName). Ensure OrderReceiptModuleService’s constructor
signature and wiring are updated to accept and provide this config so the
service no longer contains the hardcoded "N1 Shop".

In `@apps/medusa-be/src/modules/resend/service.ts`:
- Around line 143-176: The code uses unsafe casts: in renderTemplate
(templateComponents[template](data as ForgotPasswordTemplateData) etc.) and in
getAttachments ((notification as unknown as { attachments?:
NotificationAttachment[] }).attachments). Add runtime validation before casting:
implement simple type guards or validation helpers for each template shape
(e.g., isForgotPasswordData, isOrderReceiptData, isOrderPaymentReminderData) and
call them in renderTemplate before invoking templateComponents, returning null
or throwing if validation fails; for getAttachments, first treat notification as
unknown, validate it has an attachments array of NotificationAttachment-like
objects (check content/filename/contentType/path fields), then map to the
attachment shape without direct double-casting. Use the function names
renderTemplate and getAttachments and the types ProviderSendNotificationDTO and
NotificationAttachment when adding the guards to locate where to change.
- Around line 178-225: The send method currently swallows failures from
resendClient.emails.send(...) by logging and returning {}, which prevents Medusa
from marking the notification as failed; instead, when error or missing data is
returned from resendClient.emails.send in ProviderResendService.send, re-throw
or propagate a descriptive error (including the original error message and
context like notification.to and templateKey) rather than returning an empty
object so callers (e.g., sendNotificationStep) can fail and surface the delivery
problem; update the error branch in send to throw a new Error or rethrow the
caught error with added context and ensure the
Promise<ProviderSendNotificationResultsDTO> contract is preserved.

In `@apps/medusa-be/src/subscribers/reset-password.ts`:
- Around line 17-20: The thrown MedusaError uses the wrong error type for a
missing server-side env var; change the constructor call in the reset-password
subscriber (the throw new MedusaError(...) call) to use
MedusaError.Types.UNEXPECTED_STATE instead of MedusaError.Types.INVALID_DATA so
the error correctly represents a deployment/configuration problem; keep the same
message string and surrounding logic in the reset-password subscriber.

In `@apps/medusa-be/src/utils/order-payment-reminders.ts`:
- Line 123: The unguarded cast "const orders = data as PaymentReminderOrder[]"
in fetchUnpaidOrders can hide runtime shape problems; add a runtime validation
before casting by implementing/using a type guard (e.g., isPaymentReminderOrder
or isPaymentReminderOrderArray) and check Array.isArray(data) and required
fields on each item, then only assign to orders when validation passes
(otherwise handle the error/return early); mirror the same approach used to fix
fetchOrderById so both fetchUnpaidOrders and fetchOrderById validate input
before performing any "as" cast.
- Around line 96-104: fetchOrderById currently force-casts query.graph() result
with "data as PaymentReminderOrder[]" and returns [0] which can be undefined;
change the function to validate the response before casting: check that data is
an array (Array.isArray(data)) and has at least one element, optionally run a
lightweight runtime guard on the first item (e.g., check required fields from
ORDER_FIELDS) before treating it as PaymentReminderOrder, return the first
element if valid or undefined otherwise, and update the function signature to
return Promise<PaymentReminderOrder | undefined>; reference fetchOrderById,
Query.graph, ORDER_FIELDS, and PaymentReminderOrder when implementing the
checks.
- Line 93: paymentStatus is string | undefined and is being cast unsafely to
PaymentStatus; replace the unguarded cast by validating the value before
membership check. Update the call that currently uses
UNPAID_PAYMENT_STATUSES.has(paymentStatus as PaymentStatus) to first ensure
paymentStatus is a string (e.g. typeof paymentStatus === 'string') and then
check membership using a string-safe check such as
Array.from(UNPAID_PAYMENT_STATUSES).includes(paymentStatus), or alter
UNPAID_PAYMENT_STATUSES to be typed as Set<string> and use
UNPAID_PAYMENT_STATUSES.has(paymentStatus) after the typeof guard; reference
UNPAID_PAYMENT_STATUSES, paymentStatus and PaymentStatus.
- Line 18: Rename the batch-size constant BATCH_SIZE to CHUNK_SIZE and update
all usages accordingly: replace the declaration const BATCH_SIZE = 100 with
const CHUNK_SIZE = 100, change pagination: { take: BATCH_SIZE } to pagination: {
take: CHUNK_SIZE }, update conditional checks like if (orders.length <
BATCH_SIZE) to if (orders.length < CHUNK_SIZE), and update offset arithmetic
offset += BATCH_SIZE to offset += CHUNK_SIZE so all references (BATCH_SIZE) are
consistently renamed to CHUNK_SIZE.

In `@apps/medusa-be/src/workflows/send-order-receipt.ts`:
- Line 107: The code currently casts data to QueryOrder[] directly (const order
= (data as QueryOrder[])[0]) before validation; change this to first treat data
as unknown, then validate/narrow it to QueryOrder[] before accessing [0] so the
existing if (!order) guard completes the validation; locate the occurrence in
send-order-receipt.ts where data is used, replace the direct cast with an
unknown-to-QueryOrder[] narrowing flow (e.g., const maybeOrders: unknown = data;
validate/ensure Array.isArray and item shape, then const order = maybeOrders[0])
using the same variable names (data, order, QueryOrder) so callers like
sendOrderReceipt continue to work.
- Line 94: The container.resolve call in send-order-receipt.ts is using a
hardcoded "logger" string; replace it with the appropriate constant from
ContainerRegistrationKeys (e.g., ContainerRegistrationKeys.Logger) and import
ContainerRegistrationKeys at the top of the file, then change const logger =
container.resolve<Logger>("logger") to use that constant (const logger =
container.resolve<Logger>(ContainerRegistrationKeys.Logger)) so the code follows
the Modules/ContainerRegistrationKeys convention.
- Around line 87-154: sendOrderReceiptStep currently calls
notificationModuleService.createNotifications(...) directly (bypassing
sendNotificationStep), so email_log entries and webhook correlation are not
created; change the implementation to delegate notification creation to the
existing sendNotificationStep by extracting the notification payload into a
sibling step: keep orderReceiptModuleService.generateOrderReceiptAttachment and
the order/email validation in the current logic, return the prepared
notification payload (attachments, channel, data, resource_id/type, template,
to, trigger_type) from this step instead of calling createNotifications, and
then compose this step with sendNotificationStep at the workflow level (similar
to how sendOrderPaymentReminderWorkflow and sendForgotPasswordWorkflow do) so
sendNotificationStep performs the actual createNotifications call and creates
the email_log/webhook correlations. Ensure symbols referenced are
sendOrderReceiptStep, sendNotificationStep,
notificationModuleService.createNotifications, and
orderReceiptModuleService.generateOrderReceiptAttachment.

In `@apps/medusa-be/src/workflows/steps/send-notification.ts`:
- Around line 67-88: The fallback chains in getNotificationSubject,
getCustomerId, and getEmailType use logical OR (||) which incorrectly treats
empty strings/falsey values as absent; replace those chains with nullish
coalescing (??) to only fall back on null/undefined, and where empty-string
skipping was intentional (e.g., subject fields from
input.content/provider_data/data), add explicit checks (like testing length or
=== "") before falling back; update the expressions in getNotificationSubject,
getCustomerId, and getEmailType to use ?? and add explicit empty-string handling
for subject fields so behavior remains correct.

In `@apps/medusa-symmy-plugin/.medusa/server/package.json`:
- Line 4: Update the package.json metadata: replace the existing "description"
field text (currently referencing a "batch upsert endpoint for products") with a
concise summary mentioning email notification functionality (Resend
integration), order receipts, payment reminders, and webhook handling, and
update the "keywords" array to include terms like "email", "resend",
"notifications", "order-receipts", "payment-reminders", and "webhooks" instead
of just "products", "batch", "upsert", "import"; modify the "description" and
"keywords" fields in the package.json manifest so they accurately reflect the
plugin's functionality (also apply the same edits to the other package.json
entries noted).
- Around line 15-20: Update the package.json entry points and files to be
relative to its location: change "main" from "./.medusa/server/src/index.js" to
"./src/index.js", change "types" from "./.medusa/server/src/index.d.ts" to
"./src/index.d.ts", update any "exports" entries that point to
"./.medusa/server/src/index.js" to "./src/index.js", and replace the "files"
array with ["src"]; then add the actual implementation files src/index.js and
src/index.d.ts inside the same directory (the .medusa/server/src folder) so the
referenced paths exist.
- Around line 44-48: The package.json currently pins React 19; update the
dependencies "react" and "react-dom" from "^19.0.0" to "^18.2.0" (and update
"@types/react" to "^18.2.0" to match) so the admin dependencies are compatible
with Medusa v2.13.6, or alternatively upgrade Medusa to a version that supports
React 19; edit the entries for "react", "react-dom", and "@types/react" in the
shown package.json block accordingly and run install to refresh lockfile.

In `@docker-compose.yaml`:
- Around line 50-52: The docker-compose env for RESEND_WEBHOOK_SECRET currently
falls back to an empty string when both DC_N1_MEDUSA_RESEND_WEBHOOK_SECRET and
RESEND_WEBHOOK_SECRET are unset; change the substitution so it fails fast
instead of producing an empty secret: remove the empty-string default and
require a value (use the shell-style parameter expansion that throws an error
when the fallback is missing) for RESEND_WEBHOOK_SECRET so deployments error out
if neither DC_N1_MEDUSA_RESEND_WEBHOOK_SECRET nor RESEND_WEBHOOK_SECRET is
provided; update the RESEND_WEBHOOK_SECRET line (referenced by the
RESEND_WEBHOOK_SECRET/ DC_N1_MEDUSA_RESEND_WEBHOOK_SECRET/RESEND_WEBHOOK_SECRET
symbols) accordingly.

---

Outside diff comments:
In `@apps/medusa-be/package.json`:
- Around line 66-75: The package `@react-email/components` is deprecated—replace
it by removing `@react-email/components` (and any `@react-email/render` or
individual `@react-email/`* component packages) and installing react-email@latest,
then update all imports from "@react-email/components" to "react-email" (search
for import sites referencing `@react-email/`*); also review the resend entry and,
if upgrading to ^6.12.2, update any attachment schema usage from inlineContentId
to contentId and add `@react-email/render` as an optional peer only if needed;
finally verify whether `@react-email/components` and resend are actually used
anywhere and remove them from package.json if unused.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: dbf0617f-e5d9-457c-9605-ca4616b06eed

📥 Commits

Reviewing files that changed from the base of the PR and between 8b08912 and 4a29194.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (45)
  • .env.docker
  • apps/frontend-demo/src/app/api/health/route.ts
  • apps/medusa-be/.env.template
  • apps/medusa-be/medusa-config.ts
  • apps/medusa-be/package.json
  • apps/medusa-be/src/admin/routes/emails/page.tsx
  • apps/medusa-be/src/admin/widgets/order-payment-reminder.tsx
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/validators.ts
  • apps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.ts
  • apps/medusa-be/src/api/admin/orders/email-templates/route.ts
  • apps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.ts
  • apps/medusa-be/src/api/middlewares.ts
  • apps/medusa-be/src/api/webhooks/resend/route.ts
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
  • apps/medusa-be/src/modules/email-log/index.ts
  • apps/medusa-be/src/modules/email-log/migrations/.snapshot-email-log.json
  • apps/medusa-be/src/modules/email-log/migrations/.snapshot-medusa.json
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.ts
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/modules/email-log/models/email-webhook-event.ts
  • apps/medusa-be/src/modules/email-log/service.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/resend/emails/forgot-password.tsx
  • apps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsx
  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
  • apps/medusa-be/src/modules/resend/index.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/subscribers/order-placed.ts
  • apps/medusa-be/src/subscribers/reset-password.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
  • apps/medusa-be/src/utils/order-payment-reminders.ts
  • apps/medusa-be/src/utils/resend-webhook-events.ts
  • apps/medusa-be/src/workflows/send-forgot-password.ts
  • apps/medusa-be/src/workflows/send-order-payment-reminder.ts
  • apps/medusa-be/src/workflows/send-order-receipt.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-symmy-plugin/.medusa/server/package.json
  • docker-compose.yaml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (22)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Import UI components using the pattern import { ComponentName } from '@libs/ui/atoms/component-name' or '@libs/ui/molecules/component-name'

Files:

  • apps/frontend-demo/src/app/api/health/route.ts
  • apps/medusa-be/src/api/admin/orders/email-templates/route.ts
  • apps/medusa-be/src/modules/resend/index.ts
  • apps/medusa-be/src/api/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/validators.ts
  • apps/medusa-be/src/modules/email-log/index.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/subscribers/order-placed.ts
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.ts
  • apps/medusa-be/src/workflows/send-forgot-password.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/modules/email-log/models/email-webhook-event.ts
  • apps/medusa-be/src/modules/resend/emails/forgot-password.tsx
  • apps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.ts
  • apps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsx
  • apps/medusa-be/src/subscribers/reset-password.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/route.ts
  • apps/medusa-be/src/modules/email-log/service.ts
  • apps/medusa-be/medusa-config.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/admin/widgets/order-payment-reminder.tsx
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/workflows/send-order-payment-reminder.ts
  • apps/medusa-be/src/admin/routes/emails/page.tsx
  • apps/medusa-be/src/utils/resend-webhook-events.ts
  • apps/medusa-be/src/workflows/send-order-receipt.ts
  • apps/medusa-be/src/utils/order-payment-reminders.ts
  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
  • apps/medusa-be/src/api/webhooks/resend/route.ts
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
apps/medusa-be/src/api/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place custom Medusa backend API endpoints under apps/medusa-be/src/api

Files:

  • apps/medusa-be/src/api/admin/orders/email-templates/route.ts
  • apps/medusa-be/src/api/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/validators.ts
  • apps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/route.ts
  • apps/medusa-be/src/api/webhooks/resend/route.ts
apps/medusa-be/src/{api,modules,workflows,admin,subscribers,jobs}/**

📄 CodeRabbit inference engine (AGENTS.md)

In Medusa backend applications, organize custom code using the directory structure: api/, modules/, workflows/, admin/, subscribers/, jobs/

Files:

  • apps/medusa-be/src/api/admin/orders/email-templates/route.ts
  • apps/medusa-be/src/modules/resend/index.ts
  • apps/medusa-be/src/api/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/validators.ts
  • apps/medusa-be/src/modules/email-log/index.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/subscribers/order-placed.ts
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.ts
  • apps/medusa-be/src/workflows/send-forgot-password.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/modules/email-log/models/email-webhook-event.ts
  • apps/medusa-be/src/modules/resend/emails/forgot-password.tsx
  • apps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.ts
  • apps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsx
  • apps/medusa-be/src/subscribers/reset-password.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/route.ts
  • apps/medusa-be/src/modules/email-log/service.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/admin/widgets/order-payment-reminder.tsx
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/workflows/send-order-payment-reminder.ts
  • apps/medusa-be/src/admin/routes/emails/page.tsx
  • apps/medusa-be/src/workflows/send-order-receipt.ts
  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
  • apps/medusa-be/src/api/webhooks/resend/route.ts
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
apps/medusa-be/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/**/*.{ts,tsx,js,jsx}: Always use braces for if/else statements, even for single statements
Declare one variable per const/let statement; do not use multiple declarations on one line
Use nullish coalescing operator (??) instead of logical OR (||) for null/undefined defaults
Write comments explaining why code exists, not what it does; rely on self-documenting code with clear naming
Always validate data before type casting; do not use 'as Type' without prior validation

Files:

  • apps/medusa-be/src/api/admin/orders/email-templates/route.ts
  • apps/medusa-be/src/modules/resend/index.ts
  • apps/medusa-be/src/api/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/validators.ts
  • apps/medusa-be/src/modules/email-log/index.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/subscribers/order-placed.ts
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.ts
  • apps/medusa-be/src/workflows/send-forgot-password.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/modules/email-log/models/email-webhook-event.ts
  • apps/medusa-be/src/modules/resend/emails/forgot-password.tsx
  • apps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.ts
  • apps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsx
  • apps/medusa-be/src/subscribers/reset-password.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/route.ts
  • apps/medusa-be/src/modules/email-log/service.ts
  • apps/medusa-be/medusa-config.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/admin/widgets/order-payment-reminder.tsx
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/workflows/send-order-payment-reminder.ts
  • apps/medusa-be/src/admin/routes/emails/page.tsx
  • apps/medusa-be/src/utils/resend-webhook-events.ts
  • apps/medusa-be/src/workflows/send-order-receipt.ts
  • apps/medusa-be/src/utils/order-payment-reminders.ts
  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
  • apps/medusa-be/src/api/webhooks/resend/route.ts
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
apps/medusa-be/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/**/*.{ts,tsx}: Do not use non-null assertion operator (!) in TypeScript code
Annotate the type of generic field access (e.g., result[field]) as unknown before type guards
Use Modules.* and ContainerRegistrationKeys.* constants instead of hardcoding strings for container resolution

Files:

  • apps/medusa-be/src/api/admin/orders/email-templates/route.ts
  • apps/medusa-be/src/modules/resend/index.ts
  • apps/medusa-be/src/api/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/validators.ts
  • apps/medusa-be/src/modules/email-log/index.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/subscribers/order-placed.ts
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.ts
  • apps/medusa-be/src/workflows/send-forgot-password.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/modules/email-log/models/email-webhook-event.ts
  • apps/medusa-be/src/modules/resend/emails/forgot-password.tsx
  • apps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.ts
  • apps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsx
  • apps/medusa-be/src/subscribers/reset-password.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/route.ts
  • apps/medusa-be/src/modules/email-log/service.ts
  • apps/medusa-be/medusa-config.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/admin/widgets/order-payment-reminder.tsx
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/workflows/send-order-payment-reminder.ts
  • apps/medusa-be/src/admin/routes/emails/page.tsx
  • apps/medusa-be/src/utils/resend-webhook-events.ts
  • apps/medusa-be/src/workflows/send-order-receipt.ts
  • apps/medusa-be/src/utils/order-payment-reminders.ts
  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
  • apps/medusa-be/src/api/webhooks/resend/route.ts
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
apps/medusa-be/src/api/admin/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/api/admin/**/*.ts: Do not use variable name 'config' in admin routes as it shadows the exported 'config' from defineRouteConfig()
Admin routes are automatically protected and do not require auth middleware

Files:

  • apps/medusa-be/src/api/admin/orders/email-templates/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/validators.ts
  • apps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/route.ts
apps/medusa-be/src/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/**/*.ts: Use CHUNK_SIZE for batch operations instead of unbounded operations on large datasets
Use CACHING module's computeKey() for stable hash generation of cache keys
Use cache tags and clear() for bulk cache invalidation instead of individual key deletion
Use Redis-backed caching in multi-container environments instead of local variables for shared state
Use format '{identifier}_{id}' for database provider_id values (e.g., my_shipping_default)
Use dbService.sqlRaw<ResultType[]>() for raw SQL queries with explicit result type annotation
Filter JSON fields in-memory after querying if filtering is not supported in query.graph()

Files:

  • apps/medusa-be/src/api/admin/orders/email-templates/route.ts
  • apps/medusa-be/src/modules/resend/index.ts
  • apps/medusa-be/src/api/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/validators.ts
  • apps/medusa-be/src/modules/email-log/index.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/subscribers/order-placed.ts
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.ts
  • apps/medusa-be/src/workflows/send-forgot-password.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/modules/email-log/models/email-webhook-event.ts
  • apps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.ts
  • apps/medusa-be/src/subscribers/reset-password.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/route.ts
  • apps/medusa-be/src/modules/email-log/service.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/workflows/send-order-payment-reminder.ts
  • apps/medusa-be/src/utils/resend-webhook-events.ts
  • apps/medusa-be/src/workflows/send-order-receipt.ts
  • apps/medusa-be/src/utils/order-payment-reminders.ts
  • apps/medusa-be/src/api/webhooks/resend/route.ts
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
apps/medusa-be/src/api/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/api/**/*.ts: Colocate validators.ts, middlewares.ts, and route.ts files for API routes
Use req.validatedBody in route handlers for type-safe access to validated request body
Use MedusaError with appropriate error types (INVALID_DATA, NOT_FOUND, UNAUTHORIZED, NOT_ALLOWED, DUPLICATE_ERROR, CONFLICT) in API routes
Use query.graph() for flexible entity querying with field selection and filtering instead of direct service methods

Files:

  • apps/medusa-be/src/api/admin/orders/email-templates/route.ts
  • apps/medusa-be/src/api/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/validators.ts
  • apps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/route.ts
  • apps/medusa-be/src/api/webhooks/resend/route.ts
apps/medusa-be/src/modules/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place custom Medusa modules under apps/medusa-be/src/modules

Files:

  • apps/medusa-be/src/modules/resend/index.ts
  • apps/medusa-be/src/modules/email-log/index.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.ts
  • apps/medusa-be/src/modules/email-log/models/email-webhook-event.ts
  • apps/medusa-be/src/modules/resend/emails/forgot-password.tsx
  • apps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsx
  • apps/medusa-be/src/modules/email-log/service.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
apps/medusa-be/src/modules/**/index.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/modules/**/index.ts: Use Module(key, { service }) for standalone domain modules and ModuleProvider() for extending existing modules (payment, fulfillment)
Use _hooks.onApplicationStart for deferred cross-module dependency initialization instead of loaders
Use format 'fp
{identifier}_{id}' for container registration keys of fulfillment providers (e.g., fp_my_shipping_default)
Export module key as a constant (e.g., MY_CLIENT_MODULE = 'my_client') for consistent reference across files

Files:

  • apps/medusa-be/src/modules/resend/index.ts
  • apps/medusa-be/src/modules/email-log/index.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
apps/medusa-be/src/api/**/middlewares.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

Use validateAndTransformBody() middleware with Zod schemas for request body validation

Files:

  • apps/medusa-be/src/api/middlewares.ts
  • apps/medusa-be/src/api/admin/orders/[id]/email/middlewares.ts
apps/medusa-be/src/modules/**/models/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

Define unique constraints with soft-delete safe indexes including 'where: { deleted_at: null }' condition

Files:

  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/modules/email-log/models/email-webhook-event.ts
apps/medusa-be/src/subscribers/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place event subscribers under apps/medusa-be/src/subscribers

Files:

  • apps/medusa-be/src/subscribers/order-placed.ts
  • apps/medusa-be/src/subscribers/reset-password.ts
apps/medusa-be/src/workflows/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place business logic workflows under apps/medusa-be/src/workflows

Files:

  • apps/medusa-be/src/workflows/send-forgot-password.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/workflows/send-order-payment-reminder.ts
  • apps/medusa-be/src/workflows/send-order-receipt.ts
apps/medusa-be/src/workflows/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/workflows/**/*.ts: Use Workflows for cross-module orchestration instead of Query/Link in module services
Use createStep() to define workflow steps with container resolution capability
Use transform() function for data manipulation in workflows; workflow variables have no values at definition
Use when-then() for conditional execution in workflows instead of JavaScript if statements
Use useQueryGraphStep() for Query operations inside workflow steps
Use acquireLockStep(), protectedStep(), and releaseLockStep() for workflow-level locking in multi-step operations
Use StepResponse and WorkflowResponse for proper return types in workflow steps and workflows

Files:

  • apps/medusa-be/src/workflows/send-forgot-password.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/workflows/send-order-payment-reminder.ts
  • apps/medusa-be/src/workflows/send-order-receipt.ts
**/package.json

📄 CodeRabbit inference engine (CLAUDE.md)

Use pnpm CLI to add dependencies; never edit package.json directly

Files:

  • apps/medusa-be/package.json
apps/medusa-be/src/modules/**/service.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/modules/**/service.ts: Modules cannot access other modules directly; use Links (associate), Query (retrieve), or Workflows (orchestrate) for cross-module interaction
Module service must call super(container, options) in constructor; do not use super(...arguments)
Extend MedusaService for auto-CRUD generation of entity methods
Separate HTTP client logic from orchestration logic: HTTP client handles retries and parsing, service handles tokens and caching
Provider service constructors receive dependencies at request time, not module load time; rely on lazy singleton instantiation

Files:

  • apps/medusa-be/src/modules/email-log/service.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/resend/service.ts
apps/medusa-be/**/medusa-config.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/**/medusa-config.ts: Conditional module loading must be evaluated at BUILD time via environment variables, not runtime
Cross-module provider dependencies must declare 'dependencies' array in module config for injection into provider container

Files:

  • apps/medusa-be/medusa-config.ts
apps/medusa-be/src/admin/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place admin panel customizations under apps/medusa-be/src/admin

Files:

  • apps/medusa-be/src/admin/widgets/order-payment-reminder.tsx
  • apps/medusa-be/src/admin/routes/emails/page.tsx
apps/medusa-be/src/admin/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

Import admin environment variables using import.meta.env.VITE_* and .DEV, .PROD suffixes

Files:

  • apps/medusa-be/src/admin/widgets/order-payment-reminder.tsx
  • apps/medusa-be/src/admin/routes/emails/page.tsx
apps/medusa-be/src/jobs/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place background jobs under apps/medusa-be/src/jobs

Files:

  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
apps/medusa-be/src/jobs/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/jobs/**/*.ts: Lock overlapping background jobs using the LOCKING module to prevent concurrent conflicts
Use LOCKING module's execute() method with timeout handling for background jobs to prevent concurrent conflicts
Handle timed-out lock errors gracefully by catching 'Timed-out' error message in job logic

Files:

  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
🧠 Learnings (3)
📚 Learning: 2026-02-05T14:43:17.404Z
Learnt from: KaiUweCZE
Repo: NMIT-WR/new-engine PR: 324
File: apps/medusa-be/package.json:0-0
Timestamp: 2026-02-05T14:43:17.404Z
Learning: Validate and enforce React 19 compatibility across monorepo workspaces. Since Medusa UI supports React 19 via root package.json overrides and Medusa Cloud prerequisites show React 19 overrides for npm workspaces, ensure workspace root and all relevant package.json files align with React 19 (18+ requirement is satisfied). When reviewing, verify that overrides exist in the root package.json and that dependent packages in apps or packages directories declare React 19 (or compatible) in their peerDependencies or dependencies as appropriate for workspace usage.

Applied to files:

  • apps/medusa-be/package.json
📚 Learning: 2025-12-16T19:45:17.746Z
Learnt from: BleedingDev
Repo: NMIT-WR/new-engine PR: 207
File: libs/ui/src/molecules/select.tsx:50-50
Timestamp: 2025-12-16T19:45:17.746Z
Learning: When reviewing Tailwind classes in TSX/TS files, prefer using square brackets for arbitrary CSS values and complex expressions. Specifically: - Do not use the parentheses syntax (z-(--z-index)) for anything beyond simple CSS variable references; this syntax auto-wraps in var() and cannot handle calc or complex functions. - Use the square brackets syntax (e.g., h-[calc(var(--available-height)-var(--spacing-content))]) for calc expressions, var with calc, and any complex CSS expressions. This rule applies broadly to Tailwind v4 usage in TSX code across the project.

Applied to files:

  • apps/medusa-be/src/modules/resend/emails/forgot-password.tsx
  • apps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsx
  • apps/medusa-be/src/admin/widgets/order-payment-reminder.tsx
  • apps/medusa-be/src/admin/routes/emails/page.tsx
  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
📚 Learning: 2026-02-25T14:46:02.729Z
Learnt from: redeyecz
Repo: TechsioCZ/new-engine PR: 335
File: apps/zane-operator/src/db.ts:205-207
Timestamp: 2026-02-25T14:46:02.729Z
Learning: Enforce PostgreSQL 18+ as the minimum version across docker-compose files. Since the repo uses postgres:18.1-alpine (evidence in docker-compose.yaml), ensure all docker-compose service images for PostgreSQL use 18.1-alpine or newer. When reviewing, look for postgres images with a tag below 18 (e.g., postgres:<older-version>) and update to 18.1-alpine or a newer compatible tag. This guideline applies to all docker-compose YAML files that define PostgreSQL services.

Applied to files:

  • docker-compose.yaml
🪛 dotenv-linter (4.0.0)
.env.docker

[warning] 18-18: [UnorderedKey] The DC_N1_MEDUSA_RESEND_WEBHOOK_SECRET key should go before the DC_N1_NEXT_PUBLIC_GOOGLE_ADS_ID key

(UnorderedKey)

apps/medusa-be/.env.template

[warning] 28-28: [UnorderedKey] The ADMIN_CORS key should go before the STOREFRONT_URL key

(UnorderedKey)


[warning] 29-29: [UnorderedKey] The AUTH_CORS key should go before the STOREFRONT_URL key

(UnorderedKey)


[warning] 30-30: [UnorderedKey] The MEDUSA_BACKEND_URL key should go before the STOREFRONT_URL key

(UnorderedKey)


[warning] 31-31: [UnorderedKey] The RESEND_WEBHOOK_SECRET key should go before the STOREFRONT_URL key

(UnorderedKey)

🔇 Additional comments (27)
apps/frontend-demo/src/app/api/health/route.ts (1)

1-3: feat(api-health): Health route implementation looks good

Clear and correct GET handler returning a stable JSON health payload.

apps/medusa-symmy-plugin/.medusa/server/package.json (1)

50-53: LGTM!

The peer dependencies are appropriately specified with ^2.13.0, allowing any compatible 2.13.x version whilst the dev dependencies use the more specific ^2.13.6.

.env.docker (1)

15-18: LGTM!

DC_N1_MEDUSA_RESEND_WEBHOOK_SECRET is correctly placed alongside the other N1 Resend configuration variables. The dotenv-linter ordering warning is a false positive here — logical grouping is preferable to strict alphabetical order.

apps/medusa-be/src/modules/email-log/service.ts (1)

1-10: LGTM!

Clean, idiomatic use of MedusaService for auto-CRUD generation over EmailLog and EmailWebhookEvent. No constructor override needed.

apps/medusa-be/src/api/admin/orders/email-templates/route.ts (1)

1-8: LGTM!

Straightforward, correctly structured admin GET handler. No body validation is required for a read-only endpoint, and no config variable shadowing is present.

apps/medusa-be/src/api/admin/orders/[id]/email/middlewares.ts (1)

1-11: LGTM!

Correct use of validateAndTransformBody with the collocated Zod schema. The POST matcher is properly scoped to /admin/orders/:id/email.

apps/medusa-be/src/utils/resend-webhook-events.ts (1)

1-4: LGTM!

Clean, focused constant. Using a Set for O(1) lookup is the right choice for event-type gating.

apps/medusa-be/src/api/admin/orders/[id]/email/validators.ts (1)

1-9: LGTM!

Minimal and correct Zod schema. z.string().min(1) correctly rejects empty template strings, and the inferred type export is a nice touch for type-safe route handlers.

apps/medusa-be/src/modules/resend/index.ts (1)

1-9: feat(resend): correct ModuleProvider wiring for the notification extension — LGTM!

The ModuleProvider(Modules.NOTIFICATION, ...) pattern is the right choice here, and importing both ModuleProvider and Modules from @medusajs/framework/utils aligns with the project conventions.

apps/medusa-be/src/modules/email-log/index.ts (1)

1-8: feat(email-log): clean module entrypoint — LGTM!

Module(EMAIL_LOG_MODULE, { service: EmailLogModuleService }) is the correct standalone-domain pattern, and the exported EMAIL_LOG_MODULE constant ensures consistent referencing across the codebase.

apps/medusa-be/src/api/middlewares.ts (1)

10-11: feat(middlewares): clean registration of adminOrderEmailRoutesMiddlewares — LGTM!

Consistent with how the other admin middleware groups are imported and spread into routes.

Also applies to: 31-34

apps/medusa-be/src/modules/order-receipt/index.ts (1)

1-8: feat(order-receipt): clean module entrypoint — LGTM!

Consistent with the email-log module pattern: Module(ORDER_RECEIPT_MODULE, { service: OrderReceiptModuleService }) with the key exported as a constant. No concerns.

apps/medusa-be/src/modules/email-log/migrations/.snapshot-email-log.json (1)

1-172: chore(snapshot): auto-generated file — LGTM!

The snapshot faithfully reflects the EmailLog model definition. The IDX_email_log_deleted_at partial index is standard Medusa/MikroORM framework behaviour. Note that once the unique constraint on email_id is added to the model (see comment on email-log.ts), this snapshot will need to be regenerated.

apps/medusa-be/src/modules/email-log/models/email-webhook-event.ts (1)

1-25: LGTM! 👍

Model definition, index naming, and soft-delete-safe index filters all look correct.

apps/medusa-be/src/subscribers/order-placed.ts (1)

1-22: LGTM! 👍

Clean subscriber implementation — correct event binding, standard container usage, and the optional store_name is passed correctly.

apps/medusa-be/src/workflows/send-forgot-password.ts (1)

1-34: LGTM! 👍

Correct use of transform() for data preparation and WorkflowResponse for the return type — fully compliant with workflow SDK guidelines.

apps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.ts (1)

1-25: LGTM! 👍

Table definition, column types, and all four soft-delete-safe partial indexes are consistent with the model definition.

apps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.ts (1)

1-53: LGTM! 👍

Guard clauses use the correct MedusaError types, ContainerRegistrationKeys.QUERY is used for container resolution, and the workflow is invoked with the correct scope — all consistent with the codebase guidelines.

apps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.ts (1)

1-22: LGTM! 👍

Table DDL and all three soft-delete-safe partial indexes are correctly aligned with the EmailWebhookEvent model definition.

apps/medusa-be/src/modules/resend/emails/order-receipt.tsx (1)

19-65: LGTM! Clean, well-structured React Email template.

The conditional rendering pattern for optional props and the satisfies type annotation on PreviewProps are both idiomatic and correct.

apps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsx (1)

21-80: LGTM! Payment reminder email template is well-formed.

The fallback text link for the payment_url alongside the Button is a good defensive touch for email clients that block button rendering.

apps/medusa-be/src/utils/order-email-templates.ts (1)

1-25: LGTM! Clean, well-typed template catalogue.

The satisfies annotation on orderEmailTemplates and the explicit template is string narrowing on the type guard are both idiomatic TS patterns.

apps/medusa-be/src/admin/widgets/order-payment-reminder.tsx (1)

261-275: LGTM! Clever dual-mode widget pattern.

Using Partial<DetailWidgetProps<AdminOrder>> to make data optional and branching on data?.id to switch between the detail and list views is an elegant approach for registering one widget across two zones.

apps/medusa-be/src/modules/resend/emails/forgot-password.tsx (1)

19-74: LGTM! Well-structured forgot-password email template.

apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts (1)

15-15: ⚡ Quick win

Current implementation is acceptable—no shared CHUNK_SIZE constant exists in the project.

The code correctly uses a bounded batch constant (MAX_ORDERS_PER_RUN = 500), which satisfies the guideline requirement. Although other files in the codebase define local CHUNK_SIZE, BATCH_SIZE, or similar constants, there is no project-level shared constant to reference. The naming (MAX_ORDERS_PER_RUN vs CHUNK_SIZE) is a stylistic choice rather than a compliance issue.

apps/medusa-be/src/workflows/send-order-payment-reminder.ts (1)

1-51: LGTM — clean workflow implementation.

Inputs are clearly typed, delegation to sendNotificationStep is correct, and WorkflowResponse is returned as expected.

apps/medusa-be/src/workflows/steps/send-notification.ts (1)

55-55: ⚡ Quick win

The project does not have a canonical CHUNK_SIZE constant to refactor to.

The codebase uses locally-scoped, descriptively-named constants for batch operations (e.g., CUSTOMER_LOOKUP_CHUNK_SIZE, CHUNK_SIZE in inventory steps). The current implementation is consistent with this pattern and the descriptive name CUSTOMER_LOOKUP_CHUNK_SIZE clearly indicates its purpose.

			> Likely an incorrect or invalid review comment.

Comment thread apps/medusa-be/.env.template
Comment thread apps/medusa-be/medusa-config.ts Outdated
Comment thread apps/medusa-be/src/admin/routes/emails/page.tsx
Comment thread apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
Comment thread apps/medusa-be/src/api/admin/email-logs/[id]/route.ts Outdated
Comment thread apps/medusa-be/src/workflows/steps/send-notification.ts
Comment thread apps/medusa-symmy-plugin/.medusa/server/package.json Outdated
Comment thread apps/medusa-symmy-plugin/.medusa/server/package.json Outdated
Comment thread apps/medusa-symmy-plugin/.medusa/server/package.json Outdated
Comment thread docker-compose.yaml Outdated
@tomas-cm1
tomas-cm1 force-pushed the feature-resender-receipt branch from 4a29194 to bf46d18 Compare May 8, 2026 09:54
@blacksmith-sh

blacksmith-sh Bot commented May 8, 2026

Copy link
Copy Markdown

Blacksmith Account Suspended

This Blacksmith account requires additional verification. Jobs targeting Blacksmith runners will not be picked up and will remain queued until they timeout.

Please contact Blacksmith Support for assistance.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (9)
apps/medusa-be/src/workflows/send-order-receipt.ts (3)

94-94: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

refactor(workflow): hardcoded "logger" for container resolution — duplicate of past review.

Please switch to ContainerRegistrationKeys.LOGGER. The guideline mandates the use of the constants instead of string literals.

As per coding guidelines: "Use Modules.* and ContainerRegistrationKeys.* constants instead of hardcoding strings for container resolution".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/workflows/send-order-receipt.ts` at line 94, Replace the
hardcoded string key used for DI resolution with the prescribed constant: change
the container.resolve<Logger>("logger") call to use
ContainerRegistrationKeys.LOGGER; update any import to ensure
ContainerRegistrationKeys is imported where send-order-receipt.ts defines the
logger resolution so the container.resolve call uses the constant symbol instead
of the literal string.

107-107: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

refactor(workflow): unsafe as QueryOrder[] cast on data — duplicate of past review.

(data as QueryOrder[])[0] casts before any validation; please narrow via unknown first or use a runtime guard, as previously suggested.

As per coding guidelines: "Always validate data before type casting; do not use 'as Type' without prior validation".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/workflows/send-order-receipt.ts` at line 107, The current
direct cast const order = (data as QueryOrder[])[0] is unsafe; replace it with a
runtime validation: treat data as unknown, add a type guard function (e.g.
isQueryOrderArray(data: unknown): data is QueryOrder[]) that checks
Array.isArray(data) and validates required properties on the first element, then
assert const order = data[0] only after the guard passes (or handle the invalid
case with an error/early return) so you never use 'as QueryOrder[]' without
prior validation.

124-146: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

fix(workflow): receipt notifications still bypass sendNotificationStep — duplicate of past review.

notificationModuleService.createNotifications(...) is still called directly here, so the email-log entry isn't created and webhook "checked" events cannot be correlated for order receipts (unlike sendOrderPaymentReminderWorkflow and sendForgotPasswordWorkflow). The previously suggested refactor — return the prepared payload from this step and compose with sendNotificationStep at the workflow level — still applies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/workflows/send-order-receipt.ts` around lines 124 - 146,
The workflow currently calls notificationModuleService.createNotifications(...)
directly in send-order-receipt.ts which bypasses sendNotificationStep and
prevents creating the email-log/webhook correlation; instead, change the step to
return the prepared notification payload (the object passed into
createNotifications) rather than invoking
notificationModuleService.createNotifications, and update the parent workflow to
invoke sendNotificationStep (as done in sendOrderPaymentReminderWorkflow and
sendForgotPasswordWorkflow) with that payload so the email-log entry and webhook
"checked" events are created and correlated properly; reference
notificationModuleService.createNotifications, sendNotificationStep,
sendOrderPaymentReminderWorkflow and sendForgotPasswordWorkflow when making
these changes.
apps/medusa-be/src/modules/order-receipt/service.ts (2)

61-61: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

refactor(receipt): replace || with ?? and lift STORE_NAME out of the service — duplicate of past review.

Both the || fallback and the hardcoded "N1 Shop" were flagged previously and remain unchanged. The store name belongs in module options injected via the constructor (or a dedicated config env), not in shared service logic; and ?? is the mandated operator for null/undefined defaults.

As per coding guidelines: "Use nullish coalescing operator (??) instead of logical OR (||) for null/undefined defaults."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/modules/order-receipt/service.ts` at line 61, The code
uses process.env.STORE_NAME with a logical OR fallback and a hardcoded default
(const supplierName = process.env.STORE_NAME || "N1 Shop"); change this by
lifting STORE_NAME into the service/module configuration and reading it from
injected options in the service constructor (e.g., accept a
storeName/moduleOptions parameter), then replace the || fallback with the
nullish coalescing operator (??) when assigning to supplierName (or use the
injected value directly) so supplierName = injectedOptions.storeName ?? "N1
Shop" (or fail fast if no default is desired) and remove direct process.env
access from the service logic.

112-117: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

fix(receipt): silent truncation at 12 items still present — duplicate of past review.

Orders with more than 12 line items still render a truncated table whose column sums won't reconcile with the grand-total summary. Past review's options (paginate, append "and N more items…", or at minimum log a warning) still apply — this is a trust/compliance concern for a Daňový doklad.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/modules/order-receipt/service.ts` around lines 112 - 117,
The current truncation uses visibleItems = (order.items ?? []).slice(0, 12) and
then renders only those items (via commands.push(pdfText(...))), which can make
totals inconsistent; update the logic in the order receipt rendering function to
detect when (order.items ?? []).length > 12 and instead of silently slicing:
either paginate the items across pages or (minimum) render the first 12 and
append a clear summary line such as "and N more items…" after the table and also
emit a warning log (use the module/service logger) indicating items were
truncated; reference the visibleItems variable, the slice(0, 12) usage, and the
commands.push(pdfText(...)) call to locate where to add the extra text and the
logger call.
apps/medusa-be/src/modules/order-receipt/helpers.ts (2)

310-322: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

fix(helpers): Math.max in getTotal still overrides legitimate zero/discounted totals — duplicate of past review.

When order.total is explicitly set (including a fully-discounted 0), it should be trusted directly; the current Math.max(toNumber(order.total), fallbackTotal) will inflate the displayed grand total to the un-discounted fallback. The previously suggested return toNumber(order.total) (or > 0 guard if zero is intended as "missing") still applies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/modules/order-receipt/helpers.ts` around lines 310 - 322,
The getTotal function incorrectly uses Math.max(toNumber(order.total),
fallbackTotal) which overrides legitimate explicit totals (including zero);
change the behavior in getTotal so that if order.total is not null/undefined you
return toNumber(order.total) directly (trust explicit totals), and only
compute/return fallbackTotal (using getSubtotal, toNumber(order.shipping_total),
getTaxTotal, toNumber(order.discount_total)) when order.total is null or
undefined.

202-202: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

refactor(helpers): || should be ?? for currency default — duplicate of past review.

currency is string | null | undefined; per the guidelines, null/undefined defaults must use ??. Past suggestion still applies — please update to (currency ?? "CZK").toUpperCase() (or guard the empty-string case explicitly if intended).

As per coding guidelines: "Use nullish coalescing operator (??) instead of logical OR (||) for null/undefined defaults."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/modules/order-receipt/helpers.ts` at line 202, Replace the
logical-OR fallback in the normalizedCurrency assignment so null/undefined
inputs use the nullish coalescing operator: locate the const normalizedCurrency
declaration in helpers.ts and change the fallback from using "||" to "??" (i.e.,
use (currency ?? "CZK").toUpperCase()); if an empty-string should also be
treated as missing, add an explicit guard for that before calling toUpperCase().
apps/medusa-be/src/modules/resend/service.ts (2)

159-174: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

refactor(resend): unsafe as unknown as cast in getAttachments — duplicate of past review.

The double-cast notification as unknown as { attachments?: NotificationAttachment[] } and the per-template data as ... casts (e.g., line 149) still violate the guideline "Always validate data before type casting; do not use 'as Type' without prior validation." Please consider the previously suggested unknown-first narrowing or runtime guards for both renderTemplate and getAttachments.

As per coding guidelines: "Always validate data before type casting; do not use 'as Type' without prior validation."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/modules/resend/service.ts` around lines 159 - 174, The
getAttachments function uses an unsafe double-cast; instead add a runtime guard
that checks notification has a valid attachments array (e.g., typeof
notification === "object" && Array.isArray((notification as any).attachments))
and validate each item shape before mapping (check each entry has at least
content or path and optional contentType/filename), then map to the provider
format; remove the "as unknown as" cast and apply the same runtime-narrowing
pattern used in renderTemplate (validate data before casting) so you never
assume NotificationAttachment without checking first.

210-223: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

refactor(resend): silent email-send failure still swallows errors — duplicate of past review.

When resendClient.emails.send() fails, the method still logs and returns {}, so Medusa records a "successful" notification with no external_id and the order-receipt workflow happily completes. The webhook correlation problem flagged previously has not been addressed yet.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/modules/resend/service.ts` around lines 210 - 223, The
current handler around this.resendClient.emails.send(emailOptions) swallows
failures by logging and returning an empty object, causing Medusa to record a
“successful” notification without external_id; change the flow to log the error
with full details and then rethrow (or throw a new Error) instead of returning
{} so callers see the failure and the notification isn't recorded as successful.
Locate the block using this.resendClient.emails.send and replace the conditional
that currently returns {} on error with code that logs the error (include the
error object) and throws the error (or a contextual Error mentioning
resend/email send) so the failure propagates; keep the successful path that
returns { id: data.id } unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/frontend-demo/src/app/api/health/route.ts`:
- Around line 1-3: The GET route uses the global Response.json; change it to use
NextResponse.json from next/server for proper App Router integration: import {
NextResponse } from "next/server", then update the exported GET function to
return NextResponse.json({ status: "ok" }) so headers/cookies and Next.js
features behave correctly (modify the GET function and add the NextResponse
import).

In `@apps/medusa-be/src/modules/order-receipt/helpers.ts`:
- Around line 183-195: formatDate currently masks missing/invalid inputs by
returning today's date; change formatDate so that when value is null/undefined
or new Date(value) is invalid it returns an explicit empty string (""), and only
formats/returns a localized date for valid Date objects/parsable strings; update
callers that rely on the old fallback to handle the empty string or throw if
needed. Ensure you modify the formatDate function signature/returns accordingly
(function formatDate(...)) and keep the
Intl.DateTimeFormat("cs-CZ").format(date) path unchanged for valid dates.
- Around line 246-268: getItemSubtotal currently treats zero values as missing
because it converts with toNumber first and then checks > 0; change it to
explicitly check for presence (null/undefined) on the raw fields before
converting so legitimate zero subtotals/total stay zero. Specifically, in
getItemSubtotal, test item.subtotal for != null (or use the nullish coalescing
pattern) and only call toNumber(item.subtotal) when present; similarly check
item.total and item.tax_total for presence before converting and computing
Math.max(0, total - taxTotal); otherwise fall back to (toNumber(item.quantity)
|| 1) * getItemUnitPrice(item). Ensure you reference getItemSubtotal,
getItemUnitPrice and toNumber when making the changes.

In `@apps/medusa-be/src/modules/order-receipt/service.ts`:
- Around line 124-127: The unitPrice computation incorrectly uses || which
treats 0 as missing and falls back; change the logic in the unitPrice expression
to explicit numeric checks: if quantity is a positive number use
Number.isFinite(lineSubtotal) ? lineSubtotal / quantity : fallbackUnitPrice,
otherwise use Number.isFinite(lineSubtotal) ? lineSubtotal : fallbackUnitPrice;
remove the || fallback usage so a legitimate 0 subtotal or 0 unit price is
preserved (update the expression that assigns unitPrice and reference variables
quantity, lineSubtotal and fallbackUnitPrice).
- Line 127: The hardcoded VAT label "21 %" must be replaced with a computed rate
derived from item.tax_total and item.subtotal: inside the receipt generation
code where taxLabel is set (the line using const taxLabel and references to
item.tax_total / item.subtotal), compute rate = round((toNumber(item.tax_total)
/ toNumber(item.subtotal)) * 100) guarding against subtotal === 0 and when
tax_total === 0 (both should yield "0 %"); then format taxLabel as `${rate} %`
(or "0 %" fallback) so each line shows the actual percent instead of always "21
%".

In `@apps/medusa-be/src/modules/resend/service.ts`:
- Around line 159-174: In getAttachments, avoid emitting undefined attachment
fields and validate required data before forwarding to Resend: for each
NotificationAttachment (from getAttachments), strip keys with undefined values
(so do not include contentType when both contentType and content_type are
missing) and ensure each attachment has either content or path (or filename when
path absent if your upstream requires it); if an attachment lacks both content
and path, throw or log an error and fail fast rather than returning a malformed
attachment. Update getAttachments to map attachments into cleaned objects and
perform the presence check, referencing the getAttachments function and the
NotificationAttachment shape.

In `@apps/medusa-be/src/subscribers/order-placed.ts`:
- Around line 12-16: The code passes process.env.STORE_NAME directly into the
workflow input which can be undefined; update the caller of
sendOrderReceiptWorkflow(...).run to validate STORE_NAME first (e.g., throw or
log+return if missing) so the workflow receives a guaranteed string;
specifically, guard the use of process.env.STORE_NAME before constructing the
input for sendOrderReceiptWorkflow and fail fast with a clear error when
STORE_NAME is not set.

In `@apps/medusa-be/src/workflows/send-order-receipt.ts`:
- Around line 143-145: Replace the hard-coded template and trigger_type strings
in send-order-receipt.ts with the shared constants: import resendEmailTemplates
(or templates.ORDER_PLACED) and the central trigger-type constant used by the
resender flow, then set template to templates.ORDER_PLACED (or
resendEmailTemplates.ORDER_PLACED) and trigger_type to the shared
TRIGGER_ORDER_PLACED constant so the values are not duplicated or drift
independently.

---

Duplicate comments:
In `@apps/medusa-be/src/modules/order-receipt/helpers.ts`:
- Around line 310-322: The getTotal function incorrectly uses
Math.max(toNumber(order.total), fallbackTotal) which overrides legitimate
explicit totals (including zero); change the behavior in getTotal so that if
order.total is not null/undefined you return toNumber(order.total) directly
(trust explicit totals), and only compute/return fallbackTotal (using
getSubtotal, toNumber(order.shipping_total), getTaxTotal,
toNumber(order.discount_total)) when order.total is null or undefined.
- Line 202: Replace the logical-OR fallback in the normalizedCurrency assignment
so null/undefined inputs use the nullish coalescing operator: locate the const
normalizedCurrency declaration in helpers.ts and change the fallback from using
"||" to "??" (i.e., use (currency ?? "CZK").toUpperCase()); if an empty-string
should also be treated as missing, add an explicit guard for that before calling
toUpperCase().

In `@apps/medusa-be/src/modules/order-receipt/service.ts`:
- Line 61: The code uses process.env.STORE_NAME with a logical OR fallback and a
hardcoded default (const supplierName = process.env.STORE_NAME || "N1 Shop");
change this by lifting STORE_NAME into the service/module configuration and
reading it from injected options in the service constructor (e.g., accept a
storeName/moduleOptions parameter), then replace the || fallback with the
nullish coalescing operator (??) when assigning to supplierName (or use the
injected value directly) so supplierName = injectedOptions.storeName ?? "N1
Shop" (or fail fast if no default is desired) and remove direct process.env
access from the service logic.
- Around line 112-117: The current truncation uses visibleItems = (order.items
?? []).slice(0, 12) and then renders only those items (via
commands.push(pdfText(...))), which can make totals inconsistent; update the
logic in the order receipt rendering function to detect when (order.items ??
[]).length > 12 and instead of silently slicing: either paginate the items
across pages or (minimum) render the first 12 and append a clear summary line
such as "and N more items…" after the table and also emit a warning log (use the
module/service logger) indicating items were truncated; reference the
visibleItems variable, the slice(0, 12) usage, and the
commands.push(pdfText(...)) call to locate where to add the extra text and the
logger call.

In `@apps/medusa-be/src/modules/resend/service.ts`:
- Around line 159-174: The getAttachments function uses an unsafe double-cast;
instead add a runtime guard that checks notification has a valid attachments
array (e.g., typeof notification === "object" && Array.isArray((notification as
any).attachments)) and validate each item shape before mapping (check each entry
has at least content or path and optional contentType/filename), then map to the
provider format; remove the "as unknown as" cast and apply the same
runtime-narrowing pattern used in renderTemplate (validate data before casting)
so you never assume NotificationAttachment without checking first.
- Around line 210-223: The current handler around
this.resendClient.emails.send(emailOptions) swallows failures by logging and
returning an empty object, causing Medusa to record a “successful” notification
without external_id; change the flow to log the error with full details and then
rethrow (or throw a new Error) instead of returning {} so callers see the
failure and the notification isn't recorded as successful. Locate the block
using this.resendClient.emails.send and replace the conditional that currently
returns {} on error with code that logs the error (include the error object) and
throws the error (or a contextual Error mentioning resend/email send) so the
failure propagates; keep the successful path that returns { id: data.id }
unchanged.

In `@apps/medusa-be/src/workflows/send-order-receipt.ts`:
- Line 94: Replace the hardcoded string key used for DI resolution with the
prescribed constant: change the container.resolve<Logger>("logger") call to use
ContainerRegistrationKeys.LOGGER; update any import to ensure
ContainerRegistrationKeys is imported where send-order-receipt.ts defines the
logger resolution so the container.resolve call uses the constant symbol instead
of the literal string.
- Line 107: The current direct cast const order = (data as QueryOrder[])[0] is
unsafe; replace it with a runtime validation: treat data as unknown, add a type
guard function (e.g. isQueryOrderArray(data: unknown): data is QueryOrder[])
that checks Array.isArray(data) and validates required properties on the first
element, then assert const order = data[0] only after the guard passes (or
handle the invalid case with an error/early return) so you never use 'as
QueryOrder[]' without prior validation.
- Around line 124-146: The workflow currently calls
notificationModuleService.createNotifications(...) directly in
send-order-receipt.ts which bypasses sendNotificationStep and prevents creating
the email-log/webhook correlation; instead, change the step to return the
prepared notification payload (the object passed into createNotifications)
rather than invoking notificationModuleService.createNotifications, and update
the parent workflow to invoke sendNotificationStep (as done in
sendOrderPaymentReminderWorkflow and sendForgotPasswordWorkflow) with that
payload so the email-log entry and webhook "checked" events are created and
correlated properly; reference notificationModuleService.createNotifications,
sendNotificationStep, sendOrderPaymentReminderWorkflow and
sendForgotPasswordWorkflow when making these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 930c186f-d232-44ce-8ad2-332f097ff9ff

📥 Commits

Reviewing files that changed from the base of the PR and between 4a29194 and bf46d18.

📒 Files selected for processing (10)
  • apps/frontend-demo/src/app/api/health/route.ts
  • apps/medusa-be/medusa-config.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/subscribers/order-placed.ts
  • apps/medusa-be/src/workflows/send-order-receipt.ts
📜 Review details
⏰ 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: main
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Import UI components using the pattern import { ComponentName } from '@libs/ui/atoms/component-name' or '@libs/ui/molecules/component-name'

Files:

  • apps/frontend-demo/src/app/api/health/route.ts
  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/subscribers/order-placed.ts
  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
  • apps/medusa-be/src/workflows/send-order-receipt.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/medusa-config.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
apps/medusa-be/src/modules/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place custom Medusa modules under apps/medusa-be/src/modules

Files:

  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
apps/medusa-be/src/{api,modules,workflows,admin,subscribers,jobs}/**

📄 CodeRabbit inference engine (AGENTS.md)

In Medusa backend applications, organize custom code using the directory structure: api/, modules/, workflows/, admin/, subscribers/, jobs/

Files:

  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/subscribers/order-placed.ts
  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
  • apps/medusa-be/src/workflows/send-order-receipt.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
apps/medusa-be/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/**/*.{ts,tsx,js,jsx}: Always use braces for if/else statements, even for single statements
Declare one variable per const/let statement; do not use multiple declarations on one line
Use nullish coalescing operator (??) instead of logical OR (||) for null/undefined defaults
Write comments explaining why code exists, not what it does; rely on self-documenting code with clear naming
Always validate data before type casting; do not use 'as Type' without prior validation

Files:

  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/subscribers/order-placed.ts
  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
  • apps/medusa-be/src/workflows/send-order-receipt.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/medusa-config.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
apps/medusa-be/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/**/*.{ts,tsx}: Do not use non-null assertion operator (!) in TypeScript code
Annotate the type of generic field access (e.g., result[field]) as unknown before type guards
Use Modules.* and ContainerRegistrationKeys.* constants instead of hardcoding strings for container resolution

Files:

  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/subscribers/order-placed.ts
  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
  • apps/medusa-be/src/workflows/send-order-receipt.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/medusa-config.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
apps/medusa-be/src/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/**/*.ts: Use CHUNK_SIZE for batch operations instead of unbounded operations on large datasets
Use CACHING module's computeKey() for stable hash generation of cache keys
Use cache tags and clear() for bulk cache invalidation instead of individual key deletion
Use Redis-backed caching in multi-container environments instead of local variables for shared state
Use format '{identifier}_{id}' for database provider_id values (e.g., my_shipping_default)
Use dbService.sqlRaw<ResultType[]>() for raw SQL queries with explicit result type annotation
Filter JSON fields in-memory after querying if filtering is not supported in query.graph()

Files:

  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/modules/order-receipt/index.ts
  • apps/medusa-be/src/subscribers/order-placed.ts
  • apps/medusa-be/src/workflows/send-order-receipt.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/order-receipt/helpers.ts
apps/medusa-be/src/modules/**/index.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/modules/**/index.ts: Use Module(key, { service }) for standalone domain modules and ModuleProvider() for extending existing modules (payment, fulfillment)
Use _hooks.onApplicationStart for deferred cross-module dependency initialization instead of loaders
Use format 'fp
{identifier}_{id}' for container registration keys of fulfillment providers (e.g., fp_my_shipping_default)
Export module key as a constant (e.g., MY_CLIENT_MODULE = 'my_client') for consistent reference across files

Files:

  • apps/medusa-be/src/modules/order-receipt/index.ts
apps/medusa-be/src/subscribers/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place event subscribers under apps/medusa-be/src/subscribers

Files:

  • apps/medusa-be/src/subscribers/order-placed.ts
apps/medusa-be/src/workflows/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place business logic workflows under apps/medusa-be/src/workflows

Files:

  • apps/medusa-be/src/workflows/send-order-receipt.ts
apps/medusa-be/src/workflows/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/workflows/**/*.ts: Use Workflows for cross-module orchestration instead of Query/Link in module services
Use createStep() to define workflow steps with container resolution capability
Use transform() function for data manipulation in workflows; workflow variables have no values at definition
Use when-then() for conditional execution in workflows instead of JavaScript if statements
Use useQueryGraphStep() for Query operations inside workflow steps
Use acquireLockStep(), protectedStep(), and releaseLockStep() for workflow-level locking in multi-step operations
Use StepResponse and WorkflowResponse for proper return types in workflow steps and workflows

Files:

  • apps/medusa-be/src/workflows/send-order-receipt.ts
apps/medusa-be/src/modules/**/service.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/modules/**/service.ts: Modules cannot access other modules directly; use Links (associate), Query (retrieve), or Workflows (orchestrate) for cross-module interaction
Module service must call super(container, options) in constructor; do not use super(...arguments)
Extend MedusaService for auto-CRUD generation of entity methods
Separate HTTP client logic from orchestration logic: HTTP client handles retries and parsing, service handles tokens and caching
Provider service constructors receive dependencies at request time, not module load time; rely on lazy singleton instantiation

Files:

  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
apps/medusa-be/**/medusa-config.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/**/medusa-config.ts: Conditional module loading must be evaluated at BUILD time via environment variables, not runtime
Cross-module provider dependencies must declare 'dependencies' array in module config for injection into provider container

Files:

  • apps/medusa-be/medusa-config.ts
🧠 Learnings (1)
📚 Learning: 2025-12-16T19:45:17.746Z
Learnt from: BleedingDev
Repo: NMIT-WR/new-engine PR: 207
File: libs/ui/src/molecules/select.tsx:50-50
Timestamp: 2025-12-16T19:45:17.746Z
Learning: When reviewing Tailwind classes in TSX/TS files, prefer using square brackets for arbitrary CSS values and complex expressions. Specifically: - Do not use the parentheses syntax (z-(--z-index)) for anything beyond simple CSS variable references; this syntax auto-wraps in var() and cannot handle calc or complex functions. - Use the square brackets syntax (e.g., h-[calc(var(--available-height)-var(--spacing-content))]) for calc expressions, var with calc, and any complex CSS expressions. This rule applies broadly to Tailwind v4 usage in TSX code across the project.

Applied to files:

  • apps/medusa-be/src/modules/resend/emails/order-receipt.tsx
🔇 Additional comments (6)
apps/medusa-be/src/modules/resend/templates.ts (1)

3-3: chore(templates): LGTM on the new ORDER_PLACED template key.

The new entry cleanly extends the ResendEmailTemplate union and stays consistent with the existing naming pattern. 🎉

apps/medusa-be/src/modules/resend/emails/order-receipt.tsx (1)

1-65: feat(resend): clean React Email template — LGTM.

Optional props are handled with truthy guards, the satisfies OrderReceiptEmailProps annotation for PreviewProps keeps the preview shape type-checked, and the layout is straightforward.

apps/medusa-be/src/modules/order-receipt/index.ts (1)

1-8: feat(module): order-receipt module entrypoint — LGTM.

Module(ORDER_RECEIPT_MODULE, { service }) with the exported key constant follows the project's Medusa module conventions cleanly.

As per coding guidelines: "Use Module(key, { service }) for standalone domain modules" and "Export module key as a constant (e.g., MY_CLIENT_MODULE = 'my_client') for consistent reference across files".

apps/medusa-be/medusa-config.ts (1)

295-297: chore(config): order-receipt module registration — LGTM.

The new ./src/modules/order-receipt entry is correctly grouped with the other local modules and matches the module key/path layout for the Medusa backend.

As per coding guidelines: "Place custom Medusa modules under apps/medusa-be/src/modules".

apps/medusa-be/src/workflows/send-order-receipt.ts (1)

100-106: 🏗️ Heavy lift

No refactoring required. The function useQueryGraphStep() referenced in the coding guidelines does not exist in the codebase or available Medusa framework APIs. The current implementation—resolving Query from the container and calling query.graph()—is the established pattern used consistently across all workflows in the project (e.g., create-inventory-levels.ts). The code follows the actual project conventions correctly.

			> Likely an incorrect or invalid review comment.
apps/medusa-be/src/subscribers/order-placed.ts (1)

20-22: chore(subscriber): event wiring looks correct

Binding the subscriber config to order.placed is clean and aligns with the intended trigger point.

Comment thread apps/frontend-demo/src/app/api/health/route.ts
Comment thread apps/medusa-be/src/modules/order-receipt/helpers.ts
Comment thread apps/medusa-be/src/modules/order-receipt/helpers.ts
Comment thread apps/medusa-be/src/modules/order-receipt/service.ts Outdated
Comment thread apps/medusa-be/src/modules/order-receipt/service.ts Outdated
Comment thread apps/medusa-be/src/modules/resend/service.ts Outdated
Comment thread apps/medusa-be/src/subscribers/order-placed.ts
Comment thread apps/medusa-be/src/workflows/send-order-receipt.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/medusa-be/src/modules/resend/service.ts`:
- Line 10: The Resend SDK import and the this.resendClient field/constructor
wiring are dead — remove the unused import "Resend", delete the
this.resendClient property and any constructor parameter/assignment that
initializes it (in the service class where the constructor currently sets
resendClient), and update the constructor signature and class members
accordingly so there are no unused symbols; if you instead prefer to use the
SDK, route the send logic through the Resend client methods (replace manual
fetch in the send method with the SDK call) but otherwise remove the import, the
resendClient field, and its constructor wiring to eliminate dead code.
- Around line 142-173: In sendTemplateEmail, wrap the fetch + response.json flow
in an AbortController with a configurable timeout (e.g., 10s) and put the
network/JSON parsing into a try/catch so network errors, timeouts, and
invalid/non-JSON responses are caught and returned in the { data: null, error:
... } shape; after parsing, use small type-guard helpers like parseEmailResponse
and parseErrorResponse to discriminate ResendApiEmailResponse vs
ResendApiErrorResponse and return the appropriate { data, error } pair, and
ensure the AbortController is cleaned up on completion.
- Around line 158-170: Parse the response JSON into an unknown (e.g., const
payload: unknown = await response.json()) instead of casting it, then validate
and narrow it with two small guards: implement isEmailResponse(value: unknown):
value is ResendApiEmailResponse that checks value is a non-null object and has a
string id, and implement toErrorResponse(value: unknown): ResendApiErrorResponse
that safely extracts message/name/statusCode if they have the right primitives
(or returns empty fields otherwise); use isEmailResponse to populate the
successful return's data and use toErrorResponse to build the error return when
!response.ok, removing all unchecked "as" casts on payload.

In `@apps/medusa-be/src/modules/resend/templates.ts`:
- Around line 52-58: Update getResendTemplateDefinition to validate the incoming
template string exists as a key in the resendTemplateDefinitions map before
casting (e.g., use
Object.prototype.hasOwnProperty.call(resendTemplateDefinitions, template) or
template in resendTemplateDefinitions) and return ResendTemplateDefinition |
undefined instead of a non-optional type; adjust getResendTemplateSubject to
call getResendTemplateDefinition and return its ?.subject (i.e., possibly
undefined) so callers cannot dereference subject without a guard. Use the
functions getResendTemplateDefinition and getResendTemplateSubject as the
reference points for these changes.

In `@apps/medusa-be/src/utils/order-email-templates.ts`:
- Around line 23-25: The subject field currently dereferences
getResendTemplateDefinition(resendEmailTemplates.ORDER_PAYMENT_REMINDER).subject
at module load, which can throw if the lookup misses; change this to call
getResendTemplateSubject(resendEmailTemplates.ORDER_PAYMENT_REMINDER) instead
(it returns string | undefined) so the assignment is null-safe and won't throw
during import; update the subject assignment in order-email-templates.ts to use
getResendTemplateSubject rather than accessing .subject on
getResendTemplateDefinition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5a0972dc-4968-443a-a51c-b4b28de124d0

📥 Commits

Reviewing files that changed from the base of the PR and between bf46d18 and 6a11039.

📒 Files selected for processing (6)
  • apps/medusa-be/src/modules/resend/emails/forgot-password.tsx
  • apps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsx
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
💤 Files with no reviewable changes (2)
  • apps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsx
  • apps/medusa-be/src/modules/resend/emails/forgot-password.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (9)
apps/medusa-be/src/workflows/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place business logic workflows under apps/medusa-be/src/workflows

Files:

  • apps/medusa-be/src/workflows/steps/send-notification.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Import UI components using the pattern import { ComponentName } from '@libs/ui/atoms/component-name' or '@libs/ui/molecules/component-name'

Files:

  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
apps/medusa-be/src/{api,modules,workflows,admin,subscribers,jobs}/**

📄 CodeRabbit inference engine (AGENTS.md)

In Medusa backend applications, organize custom code using the directory structure: api/, modules/, workflows/, admin/, subscribers/, jobs/

Files:

  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/modules/resend/service.ts
apps/medusa-be/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/**/*.{ts,tsx,js,jsx}: Always use braces for if/else statements, even for single statements
Declare one variable per const/let statement; do not use multiple declarations on one line
Use nullish coalescing operator (??) instead of logical OR (||) for null/undefined defaults
Write comments explaining why code exists, not what it does; rely on self-documenting code with clear naming
Always validate data before type casting; do not use 'as Type' without prior validation

Files:

  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
apps/medusa-be/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/**/*.{ts,tsx}: Do not use non-null assertion operator (!) in TypeScript code
Annotate the type of generic field access (e.g., result[field]) as unknown before type guards
Use Modules.* and ContainerRegistrationKeys.* constants instead of hardcoding strings for container resolution

Files:

  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
apps/medusa-be/src/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/**/*.ts: Use CHUNK_SIZE for batch operations instead of unbounded operations on large datasets
Use CACHING module's computeKey() for stable hash generation of cache keys
Use cache tags and clear() for bulk cache invalidation instead of individual key deletion
Use Redis-backed caching in multi-container environments instead of local variables for shared state
Use format '{identifier}_{id}' for database provider_id values (e.g., my_shipping_default)
Use dbService.sqlRaw<ResultType[]>() for raw SQL queries with explicit result type annotation
Filter JSON fields in-memory after querying if filtering is not supported in query.graph()

Files:

  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
apps/medusa-be/src/workflows/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/workflows/**/*.ts: Use Workflows for cross-module orchestration instead of Query/Link in module services
Use createStep() to define workflow steps with container resolution capability
Use transform() function for data manipulation in workflows; workflow variables have no values at definition
Use when-then() for conditional execution in workflows instead of JavaScript if statements
Use useQueryGraphStep() for Query operations inside workflow steps
Use acquireLockStep(), protectedStep(), and releaseLockStep() for workflow-level locking in multi-step operations
Use StepResponse and WorkflowResponse for proper return types in workflow steps and workflows

Files:

  • apps/medusa-be/src/workflows/steps/send-notification.ts
apps/medusa-be/src/modules/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place custom Medusa modules under apps/medusa-be/src/modules

Files:

  • apps/medusa-be/src/modules/resend/templates.ts
  • apps/medusa-be/src/modules/resend/service.ts
apps/medusa-be/src/modules/**/service.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/modules/**/service.ts: Modules cannot access other modules directly; use Links (associate), Query (retrieve), or Workflows (orchestrate) for cross-module interaction
Module service must call super(container, options) in constructor; do not use super(...arguments)
Extend MedusaService for auto-CRUD generation of entity methods
Separate HTTP client logic from orchestration logic: HTTP client handles retries and parsing, service handles tokens and caching
Provider service constructors receive dependencies at request time, not module load time; rely on lazy singleton instantiation

Files:

  • apps/medusa-be/src/modules/resend/service.ts
🔇 Additional comments (2)
apps/medusa-be/src/workflows/steps/send-notification.ts (1)

62-71: ⚡ Quick win

refactor(send-notification): the || chain in getNotificationSubject still violates the ?? guideline.

The fallback chain (now including getResendTemplateSubject(input.template)) keeps using ||, so a legitimately empty subject from input.content falls through silently. Per the project rule, prefer ?? for null/undefined defaults and use an explicit empty-string check where skipping blanks is intentional.

As per coding guidelines: "Use nullish coalescing operator (??) instead of logical OR (||) for null/undefined defaults."

apps/medusa-be/src/modules/resend/service.ts (1)

212-222: ⚡ Quick win

fix(resend): silent send failure still returns {} and breaks email-log correlation.

When the Resend POST fails, the method logs the error and returns an empty result, so the Medusa notification is recorded as successful with no external_id. The downstream email_log row therefore can't be correlated with Resend webhook events. Consider throwing a MedusaError (or otherwise propagating the failure) so the workflow surfaces the delivery problem.

Comment thread apps/medusa-be/src/modules/resend/service.ts
Comment thread apps/medusa-be/src/modules/resend/service.ts
Comment thread apps/medusa-be/src/modules/resend/service.ts Outdated
Comment thread apps/medusa-be/src/modules/resend/templates.ts
Comment thread apps/medusa-be/src/utils/order-email-templates.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
apps/medusa-be/src/api/admin/email-logs/[id]/route.ts (1)

116-121: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

fix(admin-email-logs): make Resend lookup best-effort

The call to retrieveResendEmail() at line 117 lacks error handling. If Resend API is down, times out, or returns an error, the entire endpoint fails with an unhandled exception—even though the local email log is already loaded and available. This renders the admin detail view inaccessible precisely when the provider is degraded. Return resend_email: null instead, allowing the drawer to display the local record whilst the external lookup fails gracefully.

Suggested fix
  const emailLog = await emailLogService.retrieveEmailLog(id)
- const resendEmail = await retrieveResendEmail(emailLog.email_id)
+ const resendEmail = await retrieveResendEmail(emailLog.email_id).catch(
+   () => null
+ )

  res.json({
    email_log: toEmailLogResponse(emailLog),
    resend_email: resendEmail,
  })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/api/admin/email-logs/`[id]/route.ts around lines 116 -
121, The call to retrieveResendEmail(emailLog.email_id) can throw and should be
best-effort so the endpoint still returns the local record; wrap the call to
retrieveResendEmail in a try/catch around the existing sequence after emailLog
is retrieved (emailLogService.retrieveEmailLog) and if an error occurs set
resendEmail = null (optionally log the error) before calling res.json({
email_log: toEmailLogResponse(emailLog), resend_email: resendEmail }); ensure
the variable resendEmail is declared in the outer scope so it can be assigned in
the catch block.
♻️ Duplicate comments (4)
apps/medusa-be/src/modules/resend/service.ts (2)

280-291: ⚠️ Potential issue | 🟠 Major

fix(resend): propagate provider failures instead of returning {}.

Line 291 lets the workflow continue after a rejected send. Downstream, send-notification.ts then falls back to the internal notification id for email_id, which breaks later Resend lookups/webhook correlation and hides delivery failures from operators.

♻️ Suggested fix
     if (error || !data) {
-      if (error) {
-        this.logger.error(
-          `Failed to send email: ${error.message ?? "unknown Resend API error"}`
-        )
-      } else {
-        this.logger.error("Failed to send email: unknown error")
-      }
-
-      return {}
+      const message = error?.message ?? "unknown Resend API error"
+      this.logger.error(`Failed to send email: ${message}`)
+      throw new MedusaError(
+        MedusaError.Types.UNEXPECTED_STATE,
+        `Failed to send email via Resend: ${message}`
+      )
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/modules/resend/service.ts` around lines 280 - 291, The
current error branch in the send flow (after calling this.sendTemplateEmail)
swallows provider failures by returning {}; instead propagate the failure so
callers can handle it—replace the `return {}` in the error path with throwing or
returning a rejected result that includes the original error (include
error.message and the original error object) so callers like
send-notification.ts can detect provider failures; update the error branch
around the call to this.sendTemplateEmail (the block that logs `Failed to send
email: ...`) to rethrow the error (or return a failure result) rather than
returning an empty object.

124-140: ⚠️ Potential issue | 🟠 Major

fix(resend): validate attachments before mapping them into the Resend payload.

Line 125 still bypasses validation with a double cast. If a caller passes a non-array or malformed entry here, this path can either throw at .map() time or forward an invalid attachment object to Resend.

♻️ Suggested fix
   protected getAttachments(notification: ProviderSendNotificationDTO) {
-    const attachments = (
-      notification as unknown as {
-        attachments?: NotificationAttachment[]
-      }
-    ).attachments
+    const raw: unknown = notification
+    const attachments =
+      isRecord(raw) && Array.isArray(raw.attachments)
+        ? raw.attachments
+        : undefined
 
     if (!attachments?.length) {
       return
     }
 
-    return attachments.map((attachment) => ({
-      content: attachment.content,
-      contentType: attachment.contentType ?? attachment.content_type,
-      filename: attachment.filename,
-      path: attachment.path,
-    }))
+    return attachments
+      .filter((attachment): attachment is NotificationAttachment =>
+        isRecord(attachment)
+      )
+      .map((attachment) => ({
+        content: attachment.content,
+        contentType: attachment.contentType ?? attachment.content_type,
+        filename: attachment.filename,
+        path: attachment.path,
+      }))
   }

As per coding guidelines: "Always validate data before type casting; do not use 'as Type' without prior validation."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/modules/resend/service.ts` around lines 124 - 140, The
getAttachments function currently uses a double-cast to read attachments which
skips validation; replace the unsafe cast with a runtime check: read attachments
as any (or unknown) then if (!Array.isArray(attachments) || attachments.length
=== 0) return undefined; before mapping, filter the array to only include
objects that have the expected shape (e.g. typeof item === 'object' && item !==
null && item.content && (item.contentType || item.content_type) &&
(item.filename || item.path)); finally map the filtered entries to the Resend
shape (content, contentType ?? content_type, filename, path). This change in
getAttachments will ensure malformed or non-array attachments on
ProviderSendNotificationDTO / NotificationAttachment are ignored rather than
causing .map() errors or forwarding invalid payloads.
apps/medusa-be/src/utils/order-payment-reminders.ts (1)

121-129: ⚠️ Potential issue | 🟠 Major

fix(reminders): validate query.graph() results before treating them as PaymentReminderOrder[].

Line 128 and Line 148 still trust data unconditionally. That can hide bad payload shapes at runtime, and fetchOrderById can also return undefined while its current signature reads as if an order is always present.

♻️ Suggested fix
+function isPaymentReminderOrderArray(
+  value: unknown
+): value is PaymentReminderOrder[] {
+  return Array.isArray(value)
+}
+
-export async function fetchOrderById(query: Query, id: string) {
+export async function fetchOrderById(
+  query: Query,
+  id: string
+): Promise<PaymentReminderOrder | undefined> {
   const { data } = await query.graph({
     entity: "order",
     fields: ORDER_FIELDS,
     filters: { id },
   })
 
-  return (data as PaymentReminderOrder[])[0]
+  const orders = isPaymentReminderOrderArray(data) ? data : []
+  return orders[0]
 }
@@
-    const orders = data as PaymentReminderOrder[]
+    const orders = isPaymentReminderOrderArray(data) ? data : []

As per coding guidelines: "Always validate data before type casting; do not use 'as Type' without prior validation."

Also applies to: 148-148

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/utils/order-payment-reminders.ts` around lines 121 - 129,
fetchOrderById currently casts query.graph() result to PaymentReminderOrder[]
without validating the payload and claims to always return an order; update
fetchOrderById to validate that the returned `data` from `query.graph({ entity:
"order", fields: ORDER_FIELDS, filters: { id } })` is an array with at least one
object matching the expected shape (e.g., presence of required order fields)
before casting, return undefined (or adjust the function signature to allow
undefined) when validation fails, and propagate the same validation pattern for
the other use of `query.graph()` around the code referenced at the other
occurrence (the one around line 148); ensure callers handle a possibly undefined
return value.
apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts (1)

139-143: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Increment sentCount only when a reminder is actually sent

On Line 142, sentCount increments even when sendReminder returns early (Line 37) for orders without an email, which overstates the completion metric.

Proposed minimal fix
   for (const order of ordersToRemind) {
     try {
+      if (!order.email) {
+        continue
+      }
       await sendReminder(container, order)
       sentCount += 1
     } catch (error) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts` around lines 139 -
143, The loop currently increments sentCount unconditionally after calling
sendReminder, which overcounts when sendReminder returns early for orders
without an email; update sendReminder(order, container) to return a boolean
(true if a reminder was actually sent, false if skipped) and change the loop
over ordersToRemind to await the boolean result and only increment sentCount
when that result is true (i.e., if (await sendReminder(container, order))
sentCount += 1). This keeps sentCount accurate while preserving existing error
handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts`:
- Line 19: Remove the local EMAIL_LOG_LOOKUP_BATCH_SIZE constant and replace all
its usages with the shared CHUNK_SIZE constant; specifically delete the
declaration "const EMAIL_LOG_LOOKUP_BATCH_SIZE = 500", add an import (or
reference) to the shared CHUNK_SIZE constant that your codebase exposes, and
update every place that referenced EMAIL_LOG_LOOKUP_BATCH_SIZE (e.g., the email
log lookup loop/scan logic) to use CHUNK_SIZE instead so batching behavior is
consistent across jobs.
- Around line 67-77: The email-log lookup currently pages all
PAYMENT_REMINDER_TEMPLATE rows with skip/take (emailLogService.listEmailLogs)
causing a full-table scan; instead, change the logic to query only logs for the
candidate order IDs by adding an order_id IN (...) filter (chunk orderIds into
batches of EMAIL_LOG_LOOKUP_BATCH_SIZE), call emailLogService.listEmailLogs with
{ type: PAYMENT_REMINDER_TEMPLATE, order_id: { $in: chunk } } (or the service's
equivalent), collect order_id results to populate alreadyRemindedOrderIds, and
remove/replace the skip/offset pagination; apply the same change to the similar
lookup block around lines 85-90 so we only scan logs for current readyOrders.

In `@apps/medusa-be/src/modules/order-receipt/service.ts`:
- Around line 161-166: The totals block overlaps the last item rows because
tableBottom is clamped to 238 regardless of how many rows visibleItems contains,
causing summaryY to be rendered on top of items; change the logic in the order
receipt rendering to compute a proper maxRows (based on row height 22 and
available space between tableTop and the bottom margin) and if
visibleItems.length exceeds that max, paginate the item table (render remaining
items on a new page) or reduce the number of visibleItems to maxRows so
tableBottom and summaryY are calculated against the actual rows on the page;
update the code that sets tableBottom, summaryY and where pdfLine(...) is pushed
to either (a) split rendering across pages when visibleItems.length > maxRows or
(b) clamp visibleItems to maxRows before computing tableBottom, using the
existing variables tableTop, visibleItems, tableBottom, summaryY and the pdfLine
call to locate and change the logic.

---

Outside diff comments:
In `@apps/medusa-be/src/api/admin/email-logs/`[id]/route.ts:
- Around line 116-121: The call to retrieveResendEmail(emailLog.email_id) can
throw and should be best-effort so the endpoint still returns the local record;
wrap the call to retrieveResendEmail in a try/catch around the existing sequence
after emailLog is retrieved (emailLogService.retrieveEmailLog) and if an error
occurs set resendEmail = null (optionally log the error) before calling
res.json({ email_log: toEmailLogResponse(emailLog), resend_email: resendEmail
}); ensure the variable resendEmail is declared in the outer scope so it can be
assigned in the catch block.

---

Duplicate comments:
In `@apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts`:
- Around line 139-143: The loop currently increments sentCount unconditionally
after calling sendReminder, which overcounts when sendReminder returns early for
orders without an email; update sendReminder(order, container) to return a
boolean (true if a reminder was actually sent, false if skipped) and change the
loop over ordersToRemind to await the boolean result and only increment
sentCount when that result is true (i.e., if (await sendReminder(container,
order)) sentCount += 1). This keeps sentCount accurate while preserving existing
error handling.

In `@apps/medusa-be/src/modules/resend/service.ts`:
- Around line 280-291: The current error branch in the send flow (after calling
this.sendTemplateEmail) swallows provider failures by returning {}; instead
propagate the failure so callers can handle it—replace the `return {}` in the
error path with throwing or returning a rejected result that includes the
original error (include error.message and the original error object) so callers
like send-notification.ts can detect provider failures; update the error branch
around the call to this.sendTemplateEmail (the block that logs `Failed to send
email: ...`) to rethrow the error (or return a failure result) rather than
returning an empty object.
- Around line 124-140: The getAttachments function currently uses a double-cast
to read attachments which skips validation; replace the unsafe cast with a
runtime check: read attachments as any (or unknown) then if
(!Array.isArray(attachments) || attachments.length === 0) return undefined;
before mapping, filter the array to only include objects that have the expected
shape (e.g. typeof item === 'object' && item !== null && item.content &&
(item.contentType || item.content_type) && (item.filename || item.path));
finally map the filtered entries to the Resend shape (content, contentType ??
content_type, filename, path). This change in getAttachments will ensure
malformed or non-array attachments on ProviderSendNotificationDTO /
NotificationAttachment are ignored rather than causing .map() errors or
forwarding invalid payloads.

In `@apps/medusa-be/src/utils/order-payment-reminders.ts`:
- Around line 121-129: fetchOrderById currently casts query.graph() result to
PaymentReminderOrder[] without validating the payload and claims to always
return an order; update fetchOrderById to validate that the returned `data` from
`query.graph({ entity: "order", fields: ORDER_FIELDS, filters: { id } })` is an
array with at least one object matching the expected shape (e.g., presence of
required order fields) before casting, return undefined (or adjust the function
signature to allow undefined) when validation fails, and propagate the same
validation pattern for the other use of `query.graph()` around the code
referenced at the other occurrence (the one around line 148); ensure callers
handle a possibly undefined return value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 717795aa-022b-4930-a077-68d5d6615b05

📥 Commits

Reviewing files that changed from the base of the PR and between 6a11039 and 0168445.

📒 Files selected for processing (11)
  • apps/medusa-be/src/admin/routes/emails/page.tsx
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
  • apps/medusa-be/src/modules/email-log/migrations/Migration20260508114500.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
  • apps/medusa-be/src/utils/order-payment-reminders.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
📜 Review details
⏰ 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: main
🧰 Additional context used
📓 Path-based instructions (17)
apps/medusa-be/src/modules/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place custom Medusa modules under apps/medusa-be/src/modules

Files:

  • apps/medusa-be/src/modules/email-log/migrations/Migration20260508114500.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/resend/service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Import UI components using the pattern import { ComponentName } from '@libs/ui/atoms/component-name' or '@libs/ui/molecules/component-name'

Files:

  • apps/medusa-be/src/modules/email-log/migrations/Migration20260508114500.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/utils/order-payment-reminders.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
  • apps/medusa-be/src/admin/routes/emails/page.tsx
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
apps/medusa-be/src/{api,modules,workflows,admin,subscribers,jobs}/**

📄 CodeRabbit inference engine (AGENTS.md)

In Medusa backend applications, organize custom code using the directory structure: api/, modules/, workflows/, admin/, subscribers/, jobs/

Files:

  • apps/medusa-be/src/modules/email-log/migrations/Migration20260508114500.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
  • apps/medusa-be/src/admin/routes/emails/page.tsx
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
apps/medusa-be/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/**/*.{ts,tsx,js,jsx}: Always use braces for if/else statements, even for single statements
Declare one variable per const/let statement; do not use multiple declarations on one line
Use nullish coalescing operator (??) instead of logical OR (||) for null/undefined defaults
Write comments explaining why code exists, not what it does; rely on self-documenting code with clear naming
Always validate data before type casting; do not use 'as Type' without prior validation

Files:

  • apps/medusa-be/src/modules/email-log/migrations/Migration20260508114500.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/utils/order-payment-reminders.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
  • apps/medusa-be/src/admin/routes/emails/page.tsx
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
apps/medusa-be/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/**/*.{ts,tsx}: Do not use non-null assertion operator (!) in TypeScript code
Annotate the type of generic field access (e.g., result[field]) as unknown before type guards
Use Modules.* and ContainerRegistrationKeys.* constants instead of hardcoding strings for container resolution

Files:

  • apps/medusa-be/src/modules/email-log/migrations/Migration20260508114500.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/utils/order-payment-reminders.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
  • apps/medusa-be/src/admin/routes/emails/page.tsx
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
apps/medusa-be/src/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/**/*.ts: Use CHUNK_SIZE for batch operations instead of unbounded operations on large datasets
Use CACHING module's computeKey() for stable hash generation of cache keys
Use cache tags and clear() for bulk cache invalidation instead of individual key deletion
Use Redis-backed caching in multi-container environments instead of local variables for shared state
Use format '{identifier}_{id}' for database provider_id values (e.g., my_shipping_default)
Use dbService.sqlRaw<ResultType[]>() for raw SQL queries with explicit result type annotation
Filter JSON fields in-memory after querying if filtering is not supported in query.graph()

Files:

  • apps/medusa-be/src/modules/email-log/migrations/Migration20260508114500.ts
  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/utils/order-payment-reminders.ts
  • apps/medusa-be/src/utils/order-email-templates.ts
  • apps/medusa-be/src/workflows/steps/send-notification.ts
  • apps/medusa-be/src/modules/email-log/models/email-log.ts
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/resend/service.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
apps/medusa-be/src/api/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place custom Medusa backend API endpoints under apps/medusa-be/src/api

Files:

  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
apps/medusa-be/src/api/admin/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/api/admin/**/*.ts: Do not use variable name 'config' in admin routes as it shadows the exported 'config' from defineRouteConfig()
Admin routes are automatically protected and do not require auth middleware

Files:

  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
apps/medusa-be/src/api/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/api/**/*.ts: Colocate validators.ts, middlewares.ts, and route.ts files for API routes
Use req.validatedBody in route handlers for type-safe access to validated request body
Use MedusaError with appropriate error types (INVALID_DATA, NOT_FOUND, UNAUTHORIZED, NOT_ALLOWED, DUPLICATE_ERROR, CONFLICT) in API routes
Use query.graph() for flexible entity querying with field selection and filtering instead of direct service methods

Files:

  • apps/medusa-be/src/api/admin/email-logs/route.ts
  • apps/medusa-be/src/api/admin/email-logs/[id]/route.ts
apps/medusa-be/src/workflows/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place business logic workflows under apps/medusa-be/src/workflows

Files:

  • apps/medusa-be/src/workflows/steps/send-notification.ts
apps/medusa-be/src/workflows/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/workflows/**/*.ts: Use Workflows for cross-module orchestration instead of Query/Link in module services
Use createStep() to define workflow steps with container resolution capability
Use transform() function for data manipulation in workflows; workflow variables have no values at definition
Use when-then() for conditional execution in workflows instead of JavaScript if statements
Use useQueryGraphStep() for Query operations inside workflow steps
Use acquireLockStep(), protectedStep(), and releaseLockStep() for workflow-level locking in multi-step operations
Use StepResponse and WorkflowResponse for proper return types in workflow steps and workflows

Files:

  • apps/medusa-be/src/workflows/steps/send-notification.ts
apps/medusa-be/src/modules/**/models/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

Define unique constraints with soft-delete safe indexes including 'where: { deleted_at: null }' condition

Files:

  • apps/medusa-be/src/modules/email-log/models/email-log.ts
apps/medusa-be/src/jobs/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place background jobs under apps/medusa-be/src/jobs

Files:

  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
apps/medusa-be/src/jobs/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/jobs/**/*.ts: Lock overlapping background jobs using the LOCKING module to prevent concurrent conflicts
Use LOCKING module's execute() method with timeout handling for background jobs to prevent concurrent conflicts
Handle timed-out lock errors gracefully by catching 'Timed-out' error message in job logic

Files:

  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
apps/medusa-be/src/admin/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place admin panel customizations under apps/medusa-be/src/admin

Files:

  • apps/medusa-be/src/admin/routes/emails/page.tsx
apps/medusa-be/src/admin/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

Import admin environment variables using import.meta.env.VITE_* and .DEV, .PROD suffixes

Files:

  • apps/medusa-be/src/admin/routes/emails/page.tsx
apps/medusa-be/src/modules/**/service.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/modules/**/service.ts: Modules cannot access other modules directly; use Links (associate), Query (retrieve), or Workflows (orchestrate) for cross-module interaction
Module service must call super(container, options) in constructor; do not use super(...arguments)
Extend MedusaService for auto-CRUD generation of entity methods
Separate HTTP client logic from orchestration logic: HTTP client handles retries and parsing, service handles tokens and caching
Provider service constructors receive dependencies at request time, not module load time; rely on lazy singleton instantiation

Files:

  • apps/medusa-be/src/modules/order-receipt/service.ts
  • apps/medusa-be/src/modules/resend/service.ts
🧠 Learnings (2)
📚 Learning: 2025-12-16T19:45:17.746Z
Learnt from: BleedingDev
Repo: NMIT-WR/new-engine PR: 207
File: libs/ui/src/molecules/select.tsx:50-50
Timestamp: 2025-12-16T19:45:17.746Z
Learning: When reviewing Tailwind classes in TSX/TS files, prefer using square brackets for arbitrary CSS values and complex expressions. Specifically: - Do not use the parentheses syntax (z-(--z-index)) for anything beyond simple CSS variable references; this syntax auto-wraps in var() and cannot handle calc or complex functions. - Use the square brackets syntax (e.g., h-[calc(var(--available-height)-var(--spacing-content))]) for calc expressions, var with calc, and any complex CSS expressions. This rule applies broadly to Tailwind v4 usage in TSX code across the project.

Applied to files:

  • apps/medusa-be/src/admin/routes/emails/page.tsx
📚 Learning: 2026-05-07T12:06:55.558Z
Learnt from: redeyecz
Repo: TechsioCZ/new-engine PR: 375
File: apps/medusa-be/src/admin/widgets/order-payment-reminder.tsx:3-11
Timestamp: 2026-05-07T12:06:55.558Z
Learning: In Medusa admin code under `apps/medusa-be/src/admin/` (admin widgets/routes), UI components must be imported from `medusajs/ui` (the Medusa admin UI kit). Do not apply the project-wide `libs/ui/atoms/*` and `libs/ui/molecules/*` import conventions to these files, and never flag `medusajs/ui` imports in this admin directory as violating the `libs/ui` convention.

Applied to files:

  • apps/medusa-be/src/admin/routes/emails/page.tsx
🔇 Additional comments (5)
apps/medusa-be/src/utils/order-email-templates.ts (2)

1-4: fix(order-email): safe subject helper import is the right move

Nice change replacing direct template-definition access with getResendTemplateSubject; this avoids module-load failures when a template definition is missing.


15-25: refactor(order-email): type contract now matches runtime lookup behaviour

Aligning subject to string | undefined and sourcing it from getResendTemplateSubject(...) is consistent and safer for downstream handling.

apps/medusa-be/src/modules/order-receipt/service.ts (2)

61-61: fix(receipt): remove the hard-coded supplier fallback from shared module code.

process.env.STORE_NAME || "N1 Shop" is still business-specific and will also treat an empty string as “missing”. Please source the fallback from module configuration and switch this defaulting to ??.

As per coding guidelines: "Use nullish coalescing operator (??) instead of logical OR (||) for null/undefined defaults."


121-121: fix(receipt): do not coerce a zero quantity into 1.

toNumber(item.quantity) || 1 still turns a legitimate 0 into 1, so the rendered quantity can diverge from the order data. Please use an explicit finite/positive check for the fallback instead of ||.

As per coding guidelines: "Use nullish coalescing operator (??) instead of logical OR (||) for null/undefined defaults."

apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts (1)

163-177: Lock execution and timed-out lock handling are well implemented

Nice work using Modules.LOCKING.execute() with a timeout and gracefully skipping when another instance holds the lock.

Comment thread apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
Comment thread apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts Outdated
Comment thread apps/medusa-be/src/modules/order-receipt/service.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts (1)

19-19: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use shared CHUNK_SIZE instead of job-local batch constant

EMAIL_LOG_LOOKUP_BATCH_SIZE duplicates batching policy. Please switch to the shared CHUNK_SIZE constant used for batch operations to keep behaviour and tuning consistent.

As per coding guidelines, batch operations should use CHUNK_SIZE constant instead of unbounded loops.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts` at line 19,
Replace the job-local constant EMAIL_LOG_LOOKUP_BATCH_SIZE with the shared
CHUNK_SIZE constant: remove EMAIL_LOG_LOOKUP_BATCH_SIZE, import CHUNK_SIZE from
the shared constants module used across batch operations, and update any
references in unpaid-order-payment-reminders (e.g., lookup loops or paginated
queries) to use CHUNK_SIZE so batching behavior matches the global policy and
avoids unbounded loops.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts`:
- Line 19: Replace the job-local constant EMAIL_LOG_LOOKUP_BATCH_SIZE with the
shared CHUNK_SIZE constant: remove EMAIL_LOG_LOOKUP_BATCH_SIZE, import
CHUNK_SIZE from the shared constants module used across batch operations, and
update any references in unpaid-order-payment-reminders (e.g., lookup loops or
paginated queries) to use CHUNK_SIZE so batching behavior matches the global
policy and avoids unbounded loops.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d9195c13-0258-4545-9199-d6a81c019a56

📥 Commits

Reviewing files that changed from the base of the PR and between 0168445 and 82e028c.

📒 Files selected for processing (2)
  • apps/medusa-be/medusa-config.ts
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
📜 Review details
⏰ 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). (2)
  • GitHub Check: main
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Import UI components using the pattern import { ComponentName } from '@libs/ui/atoms/component-name' or '@libs/ui/molecules/component-name'

Files:

  • apps/medusa-be/medusa-config.ts
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
apps/medusa-be/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/**/*.{ts,tsx}: Run npx tsc --noEmit for typechecking before committing
Always use braces around conditional blocks, even for single statements; Biome will expand them
Declare one variable per const/let statement, not multiple on one line
Use nullish coalescing operator (??) instead of logical OR (||) for default values
Do not use non-null assertions (!); use type guards or validation instead
Annotate type as unknown when accessing dynamic object properties before applying type guards
Use comments only to explain 'why', never 'what'; self-document code with clear naming
Extract pure functions to separate files for testability without runtime dependencies
Use Modules.* and ContainerRegistrationKeys.* constants instead of hardcoding module/key strings
Do not use non-null assertions in TypeScript code; validate or use type guards instead
Validate data before type casting; never use as Type without prior validation

Files:

  • apps/medusa-be/medusa-config.ts
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
apps/medusa-be/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

Run bunx biome check --write . to lint and auto-format code

Files:

  • apps/medusa-be/medusa-config.ts
  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
apps/medusa-be/**/medusa-config.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

Use feature flag env vars in medusa-config.ts evaluated at BUILD time, not runtime

Files:

  • apps/medusa-be/medusa-config.ts
apps/medusa-be/src/jobs/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Place background jobs under apps/medusa-be/src/jobs

Files:

  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
apps/medusa-be/src/{api,modules,workflows,admin,subscribers,jobs}/**

📄 CodeRabbit inference engine (AGENTS.md)

In Medusa backend applications, organize custom code using the directory structure: api/, modules/, workflows/, admin/, subscribers/, jobs/

Files:

  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
apps/medusa-be/src/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/**/*.ts: Use const and explicit typing with dbService.sqlRaw<Type>() for SQL query results
Resolve logger using container.resolve<Logger>(ContainerRegistrationKeys.LOGGER)
Resolve Query using container.resolve<Query>(ContainerRegistrationKeys.QUERY)
Use Modules.LOCKING (not Modules.LOCK) to resolve locking services
Use Modules.CACHING (not Modules.CACHE) to resolve caching services
Throw MedusaError with appropriate type and message for error responses
Use MedusaError.Types.INVALID_DATA for 400 validation errors
Use MedusaError.Types.NOT_FOUND for 404 errors
Use MedusaError.Types.UNAUTHORIZED for 401 authentication errors
Use MedusaError.Types.NOT_ALLOWED for 400 permission errors
Use MedusaError.Types.DUPLICATE_ERROR for 422 duplicate entry errors
Use MedusaError.Types.CONFLICT for 409 conflict errors
Use caching module's computeKey() to generate stable cache keys from filters and pagination
Use caching module's get() with type assertion for cache retrieval
Use caching module's set() with TTL and tags for cache storage and bulk invalidation
Use caching module's clear() with tags to bulk-invalidate related cache entries
Always use Redis for caching in multi-container deployments instead of local variables
Batch operations using CHUNK_SIZE constant instead of unbounded loops

Files:

  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
apps/medusa-be/src/jobs/**/*.ts

📄 CodeRabbit inference engine (apps/medusa-be/CLAUDE.md)

apps/medusa-be/src/jobs/**/*.ts: Use container.resolve() to access services in jobs and loaders
Lock overlapping background jobs to prevent concurrent conflicts
Use locking module's execute() method with timeout and TTL for job synchronization
Handle locking timeout errors by checking if error message includes 'Timed-out'
Do not place test files in src/jobs/__tests__/ as Medusa loads all src/jobs/ files at runtime

Files:

  • apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts
🔇 Additional comments (2)
apps/medusa-be/medusa-config.ts (1)

290-292: LGTM!

apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts (1)

54-93: LGTM!

Also applies to: 104-149

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants