Feature resender receipt - #393
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds 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. ChangesOrder Receipt Email System
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
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. Comment |
There was a problem hiding this comment.
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 winrefactor: migrate
@react-email/componentsto unifiedreact-emailpackage
@react-email/componentsis 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 unifiedreact-emailpackage:
- Uninstall:
@react-email/components,@react-email/render, and individual component packages- Install:
react-email@latest- Update imports: from
@react-email/componentstoreact-emailRegarding
resend: upgrading from^4.7.0to^6.12.2is low-impact, with minimal breaking changes (onlyinlineContentId→contentIdin attachment schema, plus@react-email/renderbecoming 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (45)
.env.dockerapps/frontend-demo/src/app/api/health/route.tsapps/medusa-be/.env.templateapps/medusa-be/medusa-config.tsapps/medusa-be/package.jsonapps/medusa-be/src/admin/routes/emails/page.tsxapps/medusa-be/src/admin/widgets/order-payment-reminder.tsxapps/medusa-be/src/api/admin/email-logs/[id]/route.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/api/admin/orders/[id]/email/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/route.tsapps/medusa-be/src/api/admin/orders/[id]/email/validators.tsapps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.tsapps/medusa-be/src/api/admin/orders/email-templates/route.tsapps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.tsapps/medusa-be/src/api/middlewares.tsapps/medusa-be/src/api/webhooks/resend/route.tsapps/medusa-be/src/jobs/unpaid-order-payment-reminders.tsapps/medusa-be/src/modules/email-log/index.tsapps/medusa-be/src/modules/email-log/migrations/.snapshot-email-log.jsonapps/medusa-be/src/modules/email-log/migrations/.snapshot-medusa.jsonapps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.tsapps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/modules/email-log/models/email-webhook-event.tsapps/medusa-be/src/modules/email-log/service.tsapps/medusa-be/src/modules/order-receipt/helpers.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/modules/resend/emails/forgot-password.tsxapps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsxapps/medusa-be/src/modules/resend/emails/order-receipt.tsxapps/medusa-be/src/modules/resend/index.tsapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/subscribers/order-placed.tsapps/medusa-be/src/subscribers/reset-password.tsapps/medusa-be/src/utils/order-email-templates.tsapps/medusa-be/src/utils/order-payment-reminders.tsapps/medusa-be/src/utils/resend-webhook-events.tsapps/medusa-be/src/workflows/send-forgot-password.tsapps/medusa-be/src/workflows/send-order-payment-reminder.tsapps/medusa-be/src/workflows/send-order-receipt.tsapps/medusa-be/src/workflows/steps/send-notification.tsapps/medusa-symmy-plugin/.medusa/server/package.jsondocker-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.tsapps/medusa-be/src/api/admin/orders/email-templates/route.tsapps/medusa-be/src/modules/resend/index.tsapps/medusa-be/src/api/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/validators.tsapps/medusa-be/src/modules/email-log/index.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/subscribers/order-placed.tsapps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.tsapps/medusa-be/src/workflows/send-forgot-password.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/modules/email-log/models/email-webhook-event.tsapps/medusa-be/src/modules/resend/emails/forgot-password.tsxapps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.tsapps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsxapps/medusa-be/src/subscribers/reset-password.tsapps/medusa-be/src/api/admin/email-logs/[id]/route.tsapps/medusa-be/src/api/admin/orders/[id]/email/route.tsapps/medusa-be/src/modules/email-log/service.tsapps/medusa-be/medusa-config.tsapps/medusa-be/src/utils/order-email-templates.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/admin/widgets/order-payment-reminder.tsxapps/medusa-be/src/jobs/unpaid-order-payment-reminders.tsapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/workflows/steps/send-notification.tsapps/medusa-be/src/workflows/send-order-payment-reminder.tsapps/medusa-be/src/admin/routes/emails/page.tsxapps/medusa-be/src/utils/resend-webhook-events.tsapps/medusa-be/src/workflows/send-order-receipt.tsapps/medusa-be/src/utils/order-payment-reminders.tsapps/medusa-be/src/modules/resend/emails/order-receipt.tsxapps/medusa-be/src/api/webhooks/resend/route.tsapps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.tsapps/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.tsapps/medusa-be/src/api/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/validators.tsapps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.tsapps/medusa-be/src/api/admin/email-logs/[id]/route.tsapps/medusa-be/src/api/admin/orders/[id]/email/route.tsapps/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.tsapps/medusa-be/src/modules/resend/index.tsapps/medusa-be/src/api/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/validators.tsapps/medusa-be/src/modules/email-log/index.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/subscribers/order-placed.tsapps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.tsapps/medusa-be/src/workflows/send-forgot-password.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/modules/email-log/models/email-webhook-event.tsapps/medusa-be/src/modules/resend/emails/forgot-password.tsxapps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.tsapps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsxapps/medusa-be/src/subscribers/reset-password.tsapps/medusa-be/src/api/admin/email-logs/[id]/route.tsapps/medusa-be/src/api/admin/orders/[id]/email/route.tsapps/medusa-be/src/modules/email-log/service.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/admin/widgets/order-payment-reminder.tsxapps/medusa-be/src/jobs/unpaid-order-payment-reminders.tsapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/workflows/steps/send-notification.tsapps/medusa-be/src/workflows/send-order-payment-reminder.tsapps/medusa-be/src/admin/routes/emails/page.tsxapps/medusa-be/src/workflows/send-order-receipt.tsapps/medusa-be/src/modules/resend/emails/order-receipt.tsxapps/medusa-be/src/api/webhooks/resend/route.tsapps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.tsapps/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.tsapps/medusa-be/src/modules/resend/index.tsapps/medusa-be/src/api/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/validators.tsapps/medusa-be/src/modules/email-log/index.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/subscribers/order-placed.tsapps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.tsapps/medusa-be/src/workflows/send-forgot-password.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/modules/email-log/models/email-webhook-event.tsapps/medusa-be/src/modules/resend/emails/forgot-password.tsxapps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.tsapps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsxapps/medusa-be/src/subscribers/reset-password.tsapps/medusa-be/src/api/admin/email-logs/[id]/route.tsapps/medusa-be/src/api/admin/orders/[id]/email/route.tsapps/medusa-be/src/modules/email-log/service.tsapps/medusa-be/medusa-config.tsapps/medusa-be/src/utils/order-email-templates.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/admin/widgets/order-payment-reminder.tsxapps/medusa-be/src/jobs/unpaid-order-payment-reminders.tsapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/workflows/steps/send-notification.tsapps/medusa-be/src/workflows/send-order-payment-reminder.tsapps/medusa-be/src/admin/routes/emails/page.tsxapps/medusa-be/src/utils/resend-webhook-events.tsapps/medusa-be/src/workflows/send-order-receipt.tsapps/medusa-be/src/utils/order-payment-reminders.tsapps/medusa-be/src/modules/resend/emails/order-receipt.tsxapps/medusa-be/src/api/webhooks/resend/route.tsapps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.tsapps/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.tsapps/medusa-be/src/modules/resend/index.tsapps/medusa-be/src/api/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/validators.tsapps/medusa-be/src/modules/email-log/index.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/subscribers/order-placed.tsapps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.tsapps/medusa-be/src/workflows/send-forgot-password.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/modules/email-log/models/email-webhook-event.tsapps/medusa-be/src/modules/resend/emails/forgot-password.tsxapps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.tsapps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsxapps/medusa-be/src/subscribers/reset-password.tsapps/medusa-be/src/api/admin/email-logs/[id]/route.tsapps/medusa-be/src/api/admin/orders/[id]/email/route.tsapps/medusa-be/src/modules/email-log/service.tsapps/medusa-be/medusa-config.tsapps/medusa-be/src/utils/order-email-templates.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/admin/widgets/order-payment-reminder.tsxapps/medusa-be/src/jobs/unpaid-order-payment-reminders.tsapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/workflows/steps/send-notification.tsapps/medusa-be/src/workflows/send-order-payment-reminder.tsapps/medusa-be/src/admin/routes/emails/page.tsxapps/medusa-be/src/utils/resend-webhook-events.tsapps/medusa-be/src/workflows/send-order-receipt.tsapps/medusa-be/src/utils/order-payment-reminders.tsapps/medusa-be/src/modules/resend/emails/order-receipt.tsxapps/medusa-be/src/api/webhooks/resend/route.tsapps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.tsapps/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.tsapps/medusa-be/src/api/admin/orders/[id]/email/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/validators.tsapps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.tsapps/medusa-be/src/api/admin/email-logs/[id]/route.tsapps/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.tsapps/medusa-be/src/modules/resend/index.tsapps/medusa-be/src/api/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/validators.tsapps/medusa-be/src/modules/email-log/index.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/subscribers/order-placed.tsapps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.tsapps/medusa-be/src/workflows/send-forgot-password.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/modules/email-log/models/email-webhook-event.tsapps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.tsapps/medusa-be/src/subscribers/reset-password.tsapps/medusa-be/src/api/admin/email-logs/[id]/route.tsapps/medusa-be/src/api/admin/orders/[id]/email/route.tsapps/medusa-be/src/modules/email-log/service.tsapps/medusa-be/src/utils/order-email-templates.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/jobs/unpaid-order-payment-reminders.tsapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/workflows/steps/send-notification.tsapps/medusa-be/src/workflows/send-order-payment-reminder.tsapps/medusa-be/src/utils/resend-webhook-events.tsapps/medusa-be/src/workflows/send-order-receipt.tsapps/medusa-be/src/utils/order-payment-reminders.tsapps/medusa-be/src/api/webhooks/resend/route.tsapps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.tsapps/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.tsapps/medusa-be/src/api/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/middlewares.tsapps/medusa-be/src/api/admin/orders/[id]/email/validators.tsapps/medusa-be/src/api/admin/orders/payment-reminders/unpaid/route.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/api/admin/orders/[id]/payment-reminder/route.tsapps/medusa-be/src/api/admin/email-logs/[id]/route.tsapps/medusa-be/src/api/admin/orders/[id]/email/route.tsapps/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.tsapps/medusa-be/src/modules/email-log/index.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/modules/email-log/migrations/Migration20260504142000.tsapps/medusa-be/src/modules/email-log/models/email-webhook-event.tsapps/medusa-be/src/modules/resend/emails/forgot-password.tsxapps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsxapps/medusa-be/src/modules/email-log/service.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/modules/resend/emails/order-receipt.tsxapps/medusa-be/src/modules/email-log/migrations/Migration20260505101500.tsapps/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.tsapps/medusa-be/src/modules/email-log/index.tsapps/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.tsapps/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.tsapps/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.tsapps/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.tsapps/medusa-be/src/workflows/steps/send-notification.tsapps/medusa-be/src/workflows/send-order-payment-reminder.tsapps/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.tsapps/medusa-be/src/workflows/steps/send-notification.tsapps/medusa-be/src/workflows/send-order-payment-reminder.tsapps/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.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/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.tsxapps/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.tsxapps/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.tsxapps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsxapps/medusa-be/src/admin/widgets/order-payment-reminder.tsxapps/medusa-be/src/admin/routes/emails/page.tsxapps/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 goodClear and correct
GEThandler 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_SECRETis correctly placed alongside the other N1 Resend configuration variables. Thedotenv-linterordering 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
MedusaServicefor auto-CRUD generation overEmailLogandEmailWebhookEvent. No constructor override needed.apps/medusa-be/src/api/admin/orders/email-templates/route.ts (1)
1-8: LGTM!Straightforward, correctly structured admin
GEThandler. No body validation is required for a read-only endpoint, and noconfigvariable shadowing is present.apps/medusa-be/src/api/admin/orders/[id]/email/middlewares.ts (1)
1-11: LGTM!Correct use of
validateAndTransformBodywith the collocated Zod schema. ThePOSTmatcher 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
Setfor 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): correctModuleProviderwiring for the notification extension — LGTM!The
ModuleProvider(Modules.NOTIFICATION, ...)pattern is the right choice here, and importing bothModuleProviderandModulesfrom@medusajs/framework/utilsaligns 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 exportedEMAIL_LOG_MODULEconstant ensures consistent referencing across the codebase.apps/medusa-be/src/api/middlewares.ts (1)
10-11: feat(middlewares): clean registration ofadminOrderEmailRoutesMiddlewares— 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-logmodule 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
EmailLogmodel definition. TheIDX_email_log_deleted_atpartial index is standard Medusa/MikroORM framework behaviour. Note that once the unique constraint onemail_idis added to the model (see comment onemail-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_nameis passed correctly.apps/medusa-be/src/workflows/send-forgot-password.ts (1)
1-34: LGTM! 👍Correct use of
transform()for data preparation andWorkflowResponsefor 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
MedusaErrortypes,ContainerRegistrationKeys.QUERYis 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
EmailWebhookEventmodel 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
satisfiestype annotation onPreviewPropsare 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_urlalongside theButtonis 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
satisfiesannotation onorderEmailTemplatesand the explicittemplate is stringnarrowing 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 makedataoptional and branching ondata?.idto 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 winCurrent implementation is acceptable—no shared
CHUNK_SIZEconstant 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 localCHUNK_SIZE,BATCH_SIZE, or similar constants, there is no project-level shared constant to reference. The naming (MAX_ORDERS_PER_RUNvsCHUNK_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
sendNotificationStepis correct, andWorkflowResponseis returned as expected.apps/medusa-be/src/workflows/steps/send-notification.ts (1)
55-55: ⚡ Quick winThe project does not have a canonical
CHUNK_SIZEconstant to refactor to.The codebase uses locally-scoped, descriptively-named constants for batch operations (e.g.,
CUSTOMER_LOOKUP_CHUNK_SIZE,CHUNK_SIZEin inventory steps). The current implementation is consistent with this pattern and the descriptive nameCUSTOMER_LOOKUP_CHUNK_SIZEclearly indicates its purpose.> Likely an incorrect or invalid review comment.
4a29194 to
bf46d18
Compare
Blacksmith Account SuspendedThis 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. |
There was a problem hiding this comment.
Actionable comments posted: 8
♻️ Duplicate comments (9)
apps/medusa-be/src/workflows/send-order-receipt.ts (3)
94-94: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winrefactor(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 winrefactor(workflow): unsafe
as QueryOrder[]cast ondata— duplicate of past review.
(data as QueryOrder[])[0]casts before any validation; please narrow viaunknownfirst 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 liftfix(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 (unlikesendOrderPaymentReminderWorkflowandsendForgotPasswordWorkflow). The previously suggested refactor — return the prepared payload from this step and compose withsendNotificationStepat 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 winrefactor(receipt): replace
||with??and liftSTORE_NAMEout 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 liftfix(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 winfix(helpers):
Math.maxingetTotalstill overrides legitimate zero/discounted totals — duplicate of past review.When
order.totalis explicitly set (including a fully-discounted0), it should be trusted directly; the currentMath.max(toNumber(order.total), fallbackTotal)will inflate the displayed grand total to the un-discounted fallback. The previously suggestedreturn toNumber(order.total)(or> 0guard 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 winrefactor(helpers):
||should be??forcurrencydefault — duplicate of past review.
currencyisstring | 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 winrefactor(resend): unsafe
as unknown ascast ingetAttachments— duplicate of past review.The double-cast
notification as unknown as { attachments?: NotificationAttachment[] }and the per-templatedata 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 bothrenderTemplateandgetAttachments.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 winrefactor(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 noexternal_idand 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
📒 Files selected for processing (10)
apps/frontend-demo/src/app/api/health/route.tsapps/medusa-be/medusa-config.tsapps/medusa-be/src/modules/order-receipt/helpers.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/modules/resend/emails/order-receipt.tsxapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/modules/resend/templates.tsapps/medusa-be/src/subscribers/order-placed.tsapps/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.tsapps/medusa-be/src/modules/resend/templates.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/subscribers/order-placed.tsapps/medusa-be/src/modules/resend/emails/order-receipt.tsxapps/medusa-be/src/workflows/send-order-receipt.tsapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/medusa-config.tsapps/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.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/modules/resend/emails/order-receipt.tsxapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/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.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/subscribers/order-placed.tsapps/medusa-be/src/modules/resend/emails/order-receipt.tsxapps/medusa-be/src/workflows/send-order-receipt.tsapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/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.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/subscribers/order-placed.tsapps/medusa-be/src/modules/resend/emails/order-receipt.tsxapps/medusa-be/src/workflows/send-order-receipt.tsapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/medusa-config.tsapps/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.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/subscribers/order-placed.tsapps/medusa-be/src/modules/resend/emails/order-receipt.tsxapps/medusa-be/src/workflows/send-order-receipt.tsapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/medusa-config.tsapps/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.tsapps/medusa-be/src/modules/order-receipt/index.tsapps/medusa-be/src/subscribers/order-placed.tsapps/medusa-be/src/workflows/send-order-receipt.tsapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/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.tsapps/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 newORDER_PLACEDtemplate key.The new entry cleanly extends the
ResendEmailTemplateunion 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 OrderReceiptEmailPropsannotation forPreviewPropskeeps 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-receiptentry 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 liftNo refactoring required. The function
useQueryGraphStep()referenced in the coding guidelines does not exist in the codebase or available Medusa framework APIs. The current implementation—resolvingQueryfrom the container and callingquery.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 correctBinding the subscriber config to
order.placedis clean and aligns with the intended trigger point.
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
apps/medusa-be/src/modules/resend/emails/forgot-password.tsxapps/medusa-be/src/modules/resend/emails/order-payment-reminder.tsxapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/modules/resend/templates.tsapps/medusa-be/src/utils/order-email-templates.tsapps/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.tsapps/medusa-be/src/modules/resend/templates.tsapps/medusa-be/src/modules/resend/service.tsapps/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.tsapps/medusa-be/src/modules/resend/templates.tsapps/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.tsapps/medusa-be/src/modules/resend/templates.tsapps/medusa-be/src/modules/resend/service.tsapps/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.tsapps/medusa-be/src/modules/resend/templates.tsapps/medusa-be/src/modules/resend/service.tsapps/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.tsapps/medusa-be/src/modules/resend/templates.tsapps/medusa-be/src/modules/resend/service.tsapps/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.tsapps/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 winrefactor(send-notification): the
||chain ingetNotificationSubjectstill violates the??guideline.The fallback chain (now including
getResendTemplateSubject(input.template)) keeps using||, so a legitimately emptysubjectfrominput.contentfalls 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 winfix(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 downstreamemail_logrow therefore can't be correlated with Resend webhook events. Consider throwing aMedusaError(or otherwise propagating the failure) so the workflow surfaces the delivery problem.
There was a problem hiding this comment.
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 winfix(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. Returnresend_email: nullinstead, 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 | 🟠 Majorfix(resend): propagate provider failures instead of returning
{}.Line 291 lets the workflow continue after a rejected send. Downstream,
send-notification.tsthen falls back to the internal notification id foremail_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 | 🟠 Majorfix(resend): validate
attachmentsbefore 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 | 🟠 Majorfix(reminders): validate
query.graph()results before treating them asPaymentReminderOrder[].Line 128 and Line 148 still trust
dataunconditionally. That can hide bad payload shapes at runtime, andfetchOrderByIdcan also returnundefinedwhile 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 winIncrement
sentCountonly when a reminder is actually sentOn Line 142,
sentCountincrements even whensendReminderreturns 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
📒 Files selected for processing (11)
apps/medusa-be/src/admin/routes/emails/page.tsxapps/medusa-be/src/api/admin/email-logs/[id]/route.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/jobs/unpaid-order-payment-reminders.tsapps/medusa-be/src/modules/email-log/migrations/Migration20260508114500.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/modules/resend/service.tsapps/medusa-be/src/utils/order-email-templates.tsapps/medusa-be/src/utils/order-payment-reminders.tsapps/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.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/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.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/utils/order-payment-reminders.tsapps/medusa-be/src/utils/order-email-templates.tsapps/medusa-be/src/workflows/steps/send-notification.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/jobs/unpaid-order-payment-reminders.tsapps/medusa-be/src/admin/routes/emails/page.tsxapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/modules/resend/service.tsapps/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.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/workflows/steps/send-notification.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/jobs/unpaid-order-payment-reminders.tsapps/medusa-be/src/admin/routes/emails/page.tsxapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/modules/resend/service.tsapps/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.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/utils/order-payment-reminders.tsapps/medusa-be/src/utils/order-email-templates.tsapps/medusa-be/src/workflows/steps/send-notification.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/jobs/unpaid-order-payment-reminders.tsapps/medusa-be/src/admin/routes/emails/page.tsxapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/modules/resend/service.tsapps/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.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/utils/order-payment-reminders.tsapps/medusa-be/src/utils/order-email-templates.tsapps/medusa-be/src/workflows/steps/send-notification.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/jobs/unpaid-order-payment-reminders.tsapps/medusa-be/src/admin/routes/emails/page.tsxapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/modules/resend/service.tsapps/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.tsapps/medusa-be/src/api/admin/email-logs/route.tsapps/medusa-be/src/utils/order-payment-reminders.tsapps/medusa-be/src/utils/order-email-templates.tsapps/medusa-be/src/workflows/steps/send-notification.tsapps/medusa-be/src/modules/email-log/models/email-log.tsapps/medusa-be/src/jobs/unpaid-order-payment-reminders.tsapps/medusa-be/src/modules/order-receipt/service.tsapps/medusa-be/src/modules/resend/service.tsapps/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.tsapps/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.tsapps/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.tsapps/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.tsapps/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 moveNice 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 behaviourAligning
subjecttostring | undefinedand sourcing it fromgetResendTemplateSubject(...)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 into1.
toNumber(item.quantity) || 1still turns a legitimate0into1, 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 implementedNice work using
Modules.LOCKING.execute()with a timeout and gracefully skipping when another instance holds the lock.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
apps/medusa-be/src/jobs/unpaid-order-payment-reminders.ts (1)
19-19: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winUse shared
CHUNK_SIZEinstead of job-local batch constant
EMAIL_LOG_LOOKUP_BATCH_SIZEduplicates batching policy. Please switch to the sharedCHUNK_SIZEconstant used for batch operations to keep behaviour and tuning consistent.As per coding guidelines, batch operations should use
CHUNK_SIZEconstant 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
📒 Files selected for processing (2)
apps/medusa-be/medusa-config.tsapps/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.tsapps/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}: Runnpx tsc --noEmitfor typechecking before committing
Always use braces around conditional blocks, even for single statements; Biome will expand them
Declare one variable perconst/letstatement, 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 asunknownwhen 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
UseModules.*andContainerRegistrationKeys.*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 useas Typewithout prior validation
Files:
apps/medusa-be/medusa-config.tsapps/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.tsapps/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.tsevaluated 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: Useconstand explicit typing withdbService.sqlRaw<Type>()for SQL query results
Resolve logger usingcontainer.resolve<Logger>(ContainerRegistrationKeys.LOGGER)
Resolve Query usingcontainer.resolve<Query>(ContainerRegistrationKeys.QUERY)
UseModules.LOCKING(notModules.LOCK) to resolve locking services
UseModules.CACHING(notModules.CACHE) to resolve caching services
ThrowMedusaErrorwith appropriate type and message for error responses
UseMedusaError.Types.INVALID_DATAfor 400 validation errors
UseMedusaError.Types.NOT_FOUNDfor 404 errors
UseMedusaError.Types.UNAUTHORIZEDfor 401 authentication errors
UseMedusaError.Types.NOT_ALLOWEDfor 400 permission errors
UseMedusaError.Types.DUPLICATE_ERRORfor 422 duplicate entry errors
UseMedusaError.Types.CONFLICTfor 409 conflict errors
Use caching module'scomputeKey()to generate stable cache keys from filters and pagination
Use caching module'sget()with type assertion for cache retrieval
Use caching module'sset()with TTL and tags for cache storage and bulk invalidation
Use caching module'sclear()with tags to bulk-invalidate related cache entries
Always use Redis for caching in multi-container deployments instead of local variables
Batch operations usingCHUNK_SIZEconstant 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: Usecontainer.resolve()to access services in jobs and loaders
Lock overlapping background jobs to prevent concurrent conflicts
Use locking module'sexecute()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 insrc/jobs/__tests__/as Medusa loads allsrc/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
Tahle branchka je vedená nad feature-resender. Nejdřív se musí zmergovat teda feature-resender
Summary by CodeRabbit
New Features
Improvements
Removals
Chores
Migrations