- New custom server.js (replaces react-router-serve): ISO timestamps on
all console.* output and on access logs, and skip successful /healthz
polls so real traffic stays visible.
- New ProcessedWebhook table + dedupe helper keyed on
X-Shopify-Webhook-Id; stops Shopify retries from triggering a second
invoice email when the original delivery exceeded the 5s ack timeout.
- orders/create + orders/fulfilled now respond 200 immediately and run
the PDF/email work in the background so we stay under that timeout.
- pickLanguage(): non-German locales (it, fr, es, ...) now default to
English instead of falling back to German. Empty/unknown still maps to
'de' so the per-shop defaultLanguage chain keeps working.
- Tests for pickLanguage and dedupe via node --test + tsx.
- Drop wireTransferGatewayNames from ShopSettings (new migration).
- Replace string-matching with a GraphQL query against
Order.transactions[].manualPaymentGateway, the first-class flag
Shopify exposes for any merchant-defined manual payment method.
- Both webhook handlers now fetch the order on the fly to classify it,
removing the configurable gateway-names field from settings.
- New ShopSettings fields: autoEmailOnWireTransferPlaced,
autoEmailOnFulfilledNonWireTransfer, wireTransferGatewayNames.
- New Automations section in settings with two toggles + gateway list.
- orders/create webhook now fires automation 1 (wire-transfer placed).
- New orders/fulfilled webhook fires automation 2 (non-wire-transfer fulfilled).
- Shared helper services/invoice/automations.server.ts handles classification
and idempotent generate+send (skips if already sent).
- Webhook subscription for orders/fulfilled added to all 3 app tomls.
This is the non-Plus fallback for Shopify Flow, whose custom-app actions
are gated to Plus stores only.
Mirrors the layout from data/mail_template.png:
- Company name + greeting headline
- Body referencing the invoice number
- Inline logo (cid:invoice-logo) attached automatically
- Footer with mailto + website links
New template vars: {{shopEmail}}, {{shopWebsite}}.
Settings UI prefills empty fields with the defaults so users see and
can tweak them without losing the fallback.
Adds a Send button to the order action extension and a corresponding
"send" op to /api/orders/:orderId/invoice. Generates the invoice on
demand if missing, then sends via the configured SMTP.
The order-action / order-block UI extensions are hosted on
extensions.shopifycdn.com and call our app via fetch(). Without CORS
headers the browser blocked the response. authenticate.admin already
returns a cors helper and handles OPTIONS preflight - wrap every
Response with it.
Vite/React Router refused to bundle the client because
app/routes/app.settings.tsx imported the constant from a .server file
and used it inside the route component (not just loader/action), so it
could not be tree-shaken out.
Move the sentinel to logoCache.constants.ts, re-export from
logoCache.server.ts for backwards compatibility, and import the constant
from constants in the route while keeping the server-only functions
(deleteStoredLogo, storeUploadedLogo) imported from .server (they are
only referenced inside the action and get tree-shaken correctly).