Built by Sepia Software · shown on a sample store — runs the same on your Shopify storeHow it's builtHire me on Upwork

Shopify promotion engine · Admin GraphQL API

Enter a promotion once. It runs, ends and cleans up by itself.

A sample jewelry store with four campaigns: Black Friday, a weekly rotating deal, a free gift and weekend free shipping. Move the store clock to Black Friday, run sync and watch prices, badges, banners and discounts change — then undo it.

How it's built

Next.js 16 · TypeScript · a framework-free promotion engine with unit tests (node:test) · Shopify Admin GraphQL 2026-10 · Tailwind CSS 4 · Vercel.

No workflow per campaign

A campaign is a record: schedule, who, benefit, storefront messaging. One reconciler computes the state the store should be in right now and emits only the calls needed to get there. New campaign = new record, not new automation.

Regular prices are always safe

Sale prices are computed from the price book, never from the live price, so markdowns cannot compound. Every call is written to a ledger with the values it overwrote; ending or undoing a run restores them exactly.

No double discounts

Each product gets at most one price benefit: highest priority wins, the rest are skipped and reported. Marked-down products are left out of automatic discounts, and discounts are created with combinesWith off for product discounts.

Store time, DST included

Schedules are wall-clock rules in America/Los_Angeles — fixed dates, “4th Thursday of November +1”, weekly rotations. The clock change on Nov 1 does not shift starts or ends; windows are also written to Shopify so a missed sync can't leave a sale running.

Production setup vs. this build

On your storeIn this build
Real store via Admin GraphQL API 2026-10, run from Shopify Flow “Run code” or a small scheduled workerSample store in the browser; the same plan is shown as mutations instead of being sent
Campaigns and banners in metaobjects (promo_campaign, promo_banner), read by Liquid sectionsMetaobject state rendered by a React storefront preview
Ledger in a metaobject or Postgres, alerts to Slack or emailLedger in your browser (Reset sample store wipes it)
Batching under API cost limits, retries with idempotency keysOne productVariantsBulkUpdate per product

How I'd start on your store

  1. Audit what exists: Flow workflows, metaobjects, Liquid bindings, discount nodes, apps touching prices.
  2. Agree the campaign model and the price book, then dry-run the reconciler read-only against your store.
  3. Switch on one campaign type at a time, with the ledger and readiness alerts from day one.
  4. Hand over: a manager-facing form, a runbook and the test suite.
Discuss your store on Upwork