Developer Guide

The PromisePro delivery-promise widget is a self-contained <script> for product pages. It shows "order within [countdown] → arrives [date]" — but only when delivery is fast enough. On any failure it renders nothing (fail-closed), so a shopper never sees a promise the network can't keep.

The widget is platform-agnostic: it reads data-* attributes on a root element and calls the PromisePro estimate API at the same origin that served the bundle. Nothing in it is tied to a specific storefront platform.

Installation

Add two things to a product page:

<div id="promisepro-delivery"
     class="promisepro-delivery"
     data-spin="YOUR_ITEM_ID"
     data-threshold-days="3"
     data-locale="en-US"
     hidden></div>

<script src="https://api.spreetail.com/api/v1/delivery-estimate/widget.js" async></script>

The widget reads its configuration from data-* attributes, detects the page quantity input, prompts the shopper for a ZIP, calls the API, and renders the promise if delivery is fast enough.

Configuration

AttributeRequiredDefaultDescription
data-spinYes*Product id (SPIN) sent to the API as item_id. Blank → renders nothing. *Not required when data-spin-list is set (bundle mode).
data-spin-listNoJSON array of { spin, qty } bundle components. When non-empty, the widget estimates the whole bundle via a single POST (see Multi-item bundles) instead of using data-spin. spin may be a number or string; qty is optional (defaults to 1). Malformed or blank → falls back to single-item mode.
data-threshold-daysYesShow only when transit days ≤ this value. Absent → renders nothing.
data-cutoff-offsetNo20Minutes trimmed off the displayed order-by cutoff (safety buffer).
data-localeNoEnglishBCP-47 language (e.g. en-US, es). Drives copy and date formatting.
hiddenRecommendedStart hidden; the widget reveals itself only on a confirmed fast result (prevents a flash).
data-show-default-delivery-windowOptionalEnabledWhen the item is slower than data-threshold-days but still under 7 transit days, shows a "Get it between X–Y days" window instead of hiding. Lower bound = threshold− 1 (min 1); upper bound = the API's transit_days. Set to "false" to restore hide-on-slow behavior. Items ≥7 days, or any failure, still render nothing.

Implicit inputs (not attributes)

  • Quantity — read from the page. The widget scans for input[name="quantity"], .quantity__input, input.quantity[type="number"], or [data-quantity-input]. Defaults to 1 if none found; re-fetches on the input's change event.
  • ZIP — prompted from the shopper and cached in localStorage (promisepro:zip).
  • API base — the origin that served widget.js. Never configured on the page. To target an environment, load the script from that environment's host.

Multi-item bundles

For a single listing that ships as several components, set data-spin-list to a JSON array of { spin, qty } instead of (or in addition to) data-spin. When that list is non-empty, the widget switches to bundle mode: it estimates all components together with one POST /v1/delivery-estimate and renders a single combined promise.

<div id="promisepro-delivery"
     class="promisepro-delivery"
     data-spin-list='[{"spin":427936,"qty":2},{"spin":427937}]'
     data-threshold-days="3"
     data-locale="en-US"
     hidden></div>

<script src="https://api.spreetail.com/api/v1/delivery-estimate/widget.js" async></script>

Behavior in bundle mode:

  • Quantity multiplies the whole bundle. The page quantity is applied to every component — ordering N bundles requests N × each component's qty.
  • One promise, shared date. The response is the same { cutoff_time, estimated_delivery_date, transit_days } estimate as single-item mode, computed from the earliest day every component is orderable together.
  • All-or-nothing, still fail-closed. If any component can't be promised (out of stock, no route, unknown), the widget renders nothing — never a partial promise.
  • data-spin-list wins over data-spin. A non-empty list uses the bundle POST; data-spin is ignored. Each component's spin may be a number or string, and qty is optional (defaults to 1).
  • Malformed → single-item fallback. If data-spin-list isn't valid JSON or resolves to an empty array, the widget falls back to single-item mode using data-spin.

Theming

The widget renders in a Shadow DOM, so storefront CSS cannot reach its internals. Its default palette is brand-agnostic and adapts to the shopper: with no overrides it follows the shopper's prefers-color-scheme — a light card on a light system theme, a dark card on a dark one. Brand it by setting --promisepro-* CSS custom properties on the root element; they inherit across the shadow boundary and win over both auto defaults.

VariableLight defaultDark defaultControls
--promisepro-accent#0f766e#34d399Delivery date, ZIP, truck icon
--promisepro-accent-contrast#ffffff#06281bText/icon sitting on the accent (submit button)
--promisepro-urgent-color#b45309#fbbf24Countdown + clock
--promisepro-bg#ffffff#12161dCard background
--promisepro-color#14181f#f3f4f6Main text
--promisepro-muted-color#5a6572#9aa4b2Labels
--promisepro-error-color#b42318#f87171Error text
--promisepro-border-color#e2e6eargba(255,255,255,0.12)Card border
--promisepro-input-bg#f5f7f9rgba(255,255,255,0.08)ZIP input fill
--promisepro-input-border#cfd6ddrgba(255,255,255,0.25)ZIP input border
--promisepro-radius12pxCorner radius
--promisepro-margin0.75rem 0Outer margin
--promisepro-max-width400pxCard width
--promisepro-fontsystem stackFont family
--promisepro-font-size16pxFont size

The default palette meets WCAG AA contrast in both themes. If you set a custom --promisepro-accent, verify it against both a light and a dark card background.

Pinning a theme

Each variable falls back independently, so to pin a fixed look you must set the surface colors together--promisepro-bg and --promisepro-color (plus --promisepro-accent). Setting only one (e.g. just the background) leaves the others following the shopper's system theme and can produce an unreadable mix.

<!-- Fixed light card with a brand accent -->
<div id="promisepro-delivery" class="promisepro-delivery"
     data-spin="ABC123" data-threshold-days="3"
     style="--promisepro-accent:#0a7d55; --promisepro-bg:#ffffff; --promisepro-color:#14181f; --promisepro-radius:8px;"
     hidden></div>

Localization

Pass the store language as data-locale (BCP-47). The widget picks a built-in translation table by primary subtag (es-USes), falling back to English. English and Spanish ship today; dates render in the same locale. Locale is not auto-detected from the browser — if data-locale is omitted, copy defaults to English.

SPA / client-rendered sites

If the product DOM is swapped after page load, re-mount with:

window.PromisePro.init();

It is idempotent — safe to call on every route change.

Deployment notes

  • CSP: allow the PromisePro host in both script-src (to load the bundle) and connect-src (the widget calls the estimate API at that same origin).
  • API: the estimate endpoint is public, CORS-enabled, and rate-limited. It answers both the single-item GET and the bundle POST (whose JSON body triggers a CORS preflight, which the endpoint handles).

What the shopper sees

OutcomeResult
Fast enough, in stock, valid ZIPCountdown + delivery date
No ZIP yet"Enter your ZIP" prompt
Too slow / out of stock / no route / API errorNothing (hidden)

Troubleshooting

SymptomCause / fix
Blank pageA self-closing <script src="…" />. Use <script src="…" async></script>.
Widget never appearsIt's fail-closed. Check: data-spin is set and valid; data-threshold-days is set; a quantity input is detectable (or add data-quantity-input); a ZIP was entered; the API returns a fast-enough 200.
Labels are English on a non-English storePass data-locale (copy isn't auto-detected from the browser).

Full example

A product page needs three things: a quantity input (your theme's), the widget root, and the script. Theming via style is optional.

<input name="quantity" type="number" value="1" min="1" />

<div id="promisepro-delivery"
     class="promisepro-delivery"
     data-spin="ABC123"
     data-threshold-days="3"
     data-cutoff-offset="20"
     data-locale="en-US"
     style="--promisepro-accent:#0a7d55"
     hidden></div>

<script src="https://api.spreetail.com/api/v1/delivery-estimate/widget.js" async></script>


Did this page help you?