Skip to content

feat(orders): send the confirmation email the order flow never dispatched - #1703

Open
MOHITKOURAV01 wants to merge 1 commit into
AnthropicBots:mainfrom
MOHITKOURAV01:feat/1698-send-order-confirmation-email
Open

feat(orders): send the confirmation email the order flow never dispatched#1703
MOHITKOURAV01 wants to merge 1 commit into
AnthropicBots:mainfrom
MOHITKOURAV01:feat/1698-send-order-confirmation-email

Conversation

@MOHITKOURAV01

@MOHITKOURAV01 MOHITKOURAV01 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Closes #1698

What was wrong

The order-confirmation feature shipped a service, an HTML template, an admin log viewer, a robots.txt entry and a vercel.json header rule. It did not ship a caller.

$ grep -rn "sendOrderEmail" backend --include='*.js' | grep -v node_modules
backend/services/emailService.js:117:async function sendOrderEmail(toEmail, order) {
backend/services/emailService.js:249:    sendOrderEmail,
backend/tests/emailService.test.js:36:  const result = await emailService.sendOrderEmail(...)

A definition, an export, and one unit test calling it directly. createOrderService builds the order, records promo usage, converts the cart and returns a summary, and never touches the mailer. No shopper has received a confirmation since it landed.

Separately, frontend/admin-email-logs.html was linked from nowhere — reaching it meant knowing the filename.

What this does

backend/services/orderNotificationService.js — the seam between the order flow and the transport. It exists rather than the controller calling sendOrderEmail directly for two reasons:

  1. createOrderService returns a result shaped for the HTTP response (orderId, breakdown, items); sendOrderEmail reads a shape built for the template (orderNumber, subtotal, shippingAddress). Something has to translate, and doing it inline at two call sites is how the two drift.
  2. Dispatch has to be non-blocking and non-throwing, and that is a property worth stating once somewhere a test can point at.

The money comes from result.breakdown — what the pricing engine actually charged — rather than the convenience copies at the top level, so the confirmation cannot quote a different total from the invoice. Lines come from validatedItems, priced as the order was written, not as the cart submitted.

Both call sites in orderController.jscreateOrder and createOrderWithPayment — dispatch after connection.commit(), alongside markAddressUsed, and neither awaits it:

  • After the commit, because an email cannot be un-sent. Telling a customer about an order that then rolls back is strictly worse than a missing email. The worst case here is an order with no confirmation, which the admin email log makes visible and an operator can resend.
  • Not awaited, because the shopper should not sit on a spinner through an SMTP handshake, and a checkout that has already charged them and moved stock must not become a 500 because smtp.example.com timed out.

frontend/admin.html gets an Email Logs entry in the sidebar, and the log page gets a link back. The entry deliberately does not carry .admin-tab — that is the class admin.js switches panels on, and it would swallow the navigation.

Guard

backend/tests/orderConfirmationEmail.test.js, 21 cases.

The first block is the one that would have caught the original bug. A unit test of a service passes perfectly well with no caller at all — that is exactly the shape of test that shipped with #1668 — so the wiring is asserted against the controller source: the require is present, every createOrderService call site has a matching dispatch, each dispatch sits after its connection.commit() and after its createOrderService, and none is awaited.

The rest cover payload assembly (breakdown wins over the convenience fields when they disagree; a zero-priced line is kept; an empty result does not throw), the dispatch contract (skips without a recipient, resolves rather than rejects when the transport throws or the payload cannot be built, passes the outcome back), that every {{placeholder}} in the template is one sendOrderEmail substitutes — an unfilled one renders as the raw {{token}} in the customer's inbox — and that the admin log page is now reachable in both directions.

Verification

$ backend/node_modules/.bin/jest --config backend/jest.config.js tests/orderConfirmationEmail.test.js
Tests:       21 passed, 21 total

$ npm run check:boot     # backend/server.js loaded with 96 mounted layers
$ npm run check:assets   # 594 local references across 31 pages resolve
$ npm run check:a11y     # 31 pages have a skip link, a main landmark and one h1
$ npm run check:sitemap  # covers all 20 public pages

CI note — merge #1701 first

The Syntax check job fails on this branch, and it is not this change:

❌ 1 of 654 JavaScript file(s) failed to parse:
  frontend/scripts/shop.js:2499
      Unexpected end of input

frontend/scripts/shop.js is unparsable on main — the responsive refactor duplicated and interleaved its initialization block. Every open PR against this repository inherits it, and because check:syntax is the first CI job and the other two are gated on it, Backend tests and Server boots are skipped rather than run. That is why this PR shows no test result.

#1701 fixes it. Once that merges, this branch picks the fix up from main with no rebase needed — the two touch no file in common. All gates pass locally on this branch's changes:

$ npm run check:boot     ✅
$ npm run check:modules  ✅
$ npm run check:assets   ✅
$ npm run check:a11y     ✅
$ npm run check:sitemap  ✅

and I verified the whole set merges cleanly by merging all five of these branches together locally: no conflicts, check:syntax green at 659 files, and the full Jest suite at 2976 passing.

The Vercel check fails on every PR in this repository with "Authorization required to deploy" against the bhuvanshs-projects team, unrelated to any change.

…ched

emailService, the HTML template, the admin log viewer, the robots.txt entry and
the vercel.json header rule all shipped together. The caller did not. Every
reference to sendOrderEmail in the repo was its definition, its export and one
unit test that called it directly, so no shopper has ever received a
confirmation.

Add orderNotificationService as the seam between the two. createOrderService
returns a result shaped for the HTTP response and sendOrderEmail reads a shape
built for the template, so something has to translate; doing it inline at both
call sites is how the two drift. It also makes the dispatch contract -- never
blocking, never throwing -- a property a test can point at.

Both call sites dispatch after connection.commit(), and neither awaits it. An
email cannot be un-sent, so telling a customer about an order that then rolls
back is strictly worse than a missing email, and a checkout that has committed
must not turn into a 500 because an SMTP handshake timed out. The money comes
from the pricing breakdown rather than the convenience copies of it, so the
confirmation cannot quote a different total from the invoice.

Also link admin-email-logs.html from the admin sidebar and give it a way back.
The page was excluded from the sitemap and disallowed in robots.txt but linked
from nowhere, so reaching it meant knowing the filename.
@hydra-maintainer

Copy link
Copy Markdown

🔍 Quality Gate Report

✅ All quality gates passed!

Status Check Details
Linked Issue PR description references a closing issue ✅

@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Bhuvansh's projects Team on Vercel.

A member of the Team first needs to authorize it.

@hydra-maintainer

Copy link
Copy Markdown

🤖 AI Code Review

🔴 Score: 50/100 | comment

AI review unavailable at this time.


Automated AI review — a human maintainer will also review.

@hydra-maintainer

Copy link
Copy Markdown

💡 Suggested reviewers based on relevant file history: @Aditya8369, @Pcmhacker-hero

@hydra-maintainer

Copy link
Copy Markdown

🟡 PR Health Score: 55/100

This PR's health score is below the 75/100 threshold for a healthy label.

Status Signal
Test coverage
Linked issue
PR description (≥50 chars)
DCO sign-off
⬜ 0 approval(s) Approvals
Diff size < 400 lines

Improving these signals will help reviewers engage faster and raise your score. 💪

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Order confirmation emails are never sent — sendOrderEmail has no caller, and admin-email-logs.html is unreachable

1 participant