Skip to content
littleworksdocs

Explore the documentation

Meet LittleworksA little backend for what you’re building on Shopify.Littleworks or your own appWhat it takes to give a Shopify feature a backend: with Littleworks, or with a custom app you build and run yourself.Connect to LittleworksChoose the CLI, a plugin, or a remote MCP connection.CLIConnect from your terminal with Shopify approval and no manual tokens.Claude pluginThe Littleworks MCP connection and agent guidance in one package.OpenAI pluginThe Littleworks MCP connection and agent guidance in one package.MCP with OAuthConnect a compatible client directly, without a marketplace plugin.Build with an agentThe operating guide for agents building on Shopify with Littleworks.Write a workA JavaScript handler, a small manifest, and a version you test and then publish.Keep secretsEncrypted credentials, shared across your store’s works.Call external servicesMake public HTTPS API requests from a work using http.fetch().Store dataDocument collections with a small API, scoped automatically to the work that uses them.Call ShopifyCall Shopify’s Admin and Storefront GraphQL APIs with credentials held by Littleworks.Store filesKeep uploads such as artwork and photos in the merchant’s Shopify Files, uploaded by the browser straight to Shopify.Expose an endpointConnect an existing frontend to a work through a small JSON API.Customer access and invitationsRequire a signed-in shopper or a narrow, expiring invitation before a work runs.RecipesUseful things to build for your store, with the backend already taken care of.Product reviewsCollect customer reviews, verify purchases, and publish approved content directly into your Shopify theme.Customer wishlistsGive signed-in customers a persistent list of products they can revisit across devices.Customer quote requestsCollect a customer’s products, quantities, and requirements without turning a request into an order.Back-in-stock alertsCollect confirmed signups on sold-out variants, email them through your own provider when stock returns, and see every signup and send in your admin.Transactional emailSend email from your works through your own provider, never twice for the same thing, and see what was sent and what failed.Build a merchant pageDescribe your records and what merchants do with them; Littleworks renders a native Shopify admin page.Run works on their ownRun a work when something happens in the store, on a schedule, or from a Shopify Flow workflow.MCP tool referenceTools to inspect, deploy, run, and organize the backend for a connected store.Usage and allowancesSee what your works use, how much capacity remains, and when allowances reset.Inspect and troubleshootFind the relevant run, understand the failure, and make the next change deliberately.Limits and securityThe current runtime boundaries, data isolation model, and execution allowances.
Shopify admin
Recipes
Markdown

Back-in-stock alerts

Collect confirmed signups on sold-out variants, email them through your own provider when stock returns, and see every signup and send in your admin.

When a variant sells out, the product page offers “Email me when it’s back”. Shoppers confirm their address from a confirmation email, so nobody can sign someone else up. When the variant can be bought again, Littleworks emails everyone waiting through your Postmark or Resend account, in signup order, and never emails anyone twice for the same signup. Every alert has an unsubscribe link. Your staff see who is waiting, what was sent and anything that failed on a page in Shopify admin.

Give this to your agent
Use Littleworks to build back-in-stock alerts for my Shopify store. When the selected variant is sold out, let shoppers enter their email on the product page. Confirm each signup with a double opt-in email, and include an unsubscribe link in every email. When the variant can be bought again, email everyone waiting through my Postmark account (I’ll add the server token in Littleworks secrets), in signup order, without ever emailing anyone twice for the same signup. Add a Littleworks admin page where staff can see signups, what was sent and anything that failed. Read https://littleworks.app/docs/recipe-back-in-stock.md and adapt it to my store, theme and email provider.

What you are building

  • On the product page: a theme block that appears only while the selected variant is sold out, with an email field and a button. It follows variant changes.
  • A confirmation page: one storefront page, /pages/back-in-stock, that email links open to confirm alerts or stop them. Nothing happens until the shopper clicks.
  • Sending: a scheduled work notices restocks and sends in batches, oldest signup first. An optional Shopify Flow workflow sends the first batch within seconds of a restock.
  • An admin page, “Back-in-stock alerts”: tabs for Waiting, Awaiting confirmation, Notified, Not delivered, Unsubscribed and All; counts; an emails-sent-per-day chart; and Send again, Cancel alert and Delete actions.

An App Store app does this too. Building it keeps the parts that are yours: your own email provider, sender domain and templates; your own rules, such as signup order, sending only as many as you have stock for, or VIP customers first; and the signups in your own collections, next to everything else you build on the same backend.

Littleworks providesYour build provides
The Shopify app, its permissions and the Storefront access the works useThe signup rules and the unsubscribe experience
Schedules, the Shopify Flow action, and rate limiting and input validation on the public signup endpointThe email templates and what they say
Encrypted secrets for the provider key, versions and rollback, run logs, and the admin pageThe theme block and the confirmation page

Decide how a restock is noticed

A restock here is what the shopper sees: the variant’s availableForSale turns true in the Storefront API. That already accounts for every location, the inventory policy and Online Store publication.

SignalHowRuns it costsHow fast
Scheduled check (every build)back_in_stock.dispatch runs on a schedule. One Storefront query checks up to 100 watched variants.Fixed by the schedule: 720 a month hourly, 1,440 every 30 minutes, 2,880 every 15 minutes. Sales volume doesn’t change it.Within one interval
Shopify Flow (optional fast path)The merchant adds a workflow: Shopify’s inventory trigger, a condition that continues only when the variant goes from none available to some, and Run a Littleworks work with back_in_stock.restocked.About one per restockSeconds
`inventory_levels/update` event (not for busy stores)An event work with a when filter.One run per inventory change while the variant is in stock, about one per order line plus adjustments: 2,000 orders of two lines is 4,000 or more a month. Needs read_inventory.Seconds

Every build gets the scheduled check, because it is also the sender. Add the Flow path when the merchant wants alerts within seconds and agrees to set up one workflow. The event path costs a run per sale because when can’t compare with the previous quantity or with the variants you are watching, so mention it only with its cost.

All paths meet at one watches document per variant, changed only with its revision: waiting, then sending, then deleted once everyone is emailed (or back to waiting if it sells out again). A restock seen by both Flow and the schedule is sent once.

Where the data lives

Use feature back_in_stock.

CollectionDocumentKept
signupsOne per signup: email (trimmed, lowercased), variantId, productId, title snapshots, status (pending, confirmed, notified, failed, unsubscribed), reason, openKey, confirmHash, test, and timestampsPending 48 hours; confirmed 180 days (the merchant’s choice); ended signups 30 days, for the page
watchesOne per watched variant, with a stable ID: the numeric variant ID, prefixed test- for test signups. status, restockedAt, inflight (the batch a run has claimed), lastError, failuresDeleted by the dispatcher once nobody is waiting
sendsOne per provider batch: count, accepted, rejected, status (sending, sent, partial, failed, unknown), runId, times90 days; feeds the metrics and chart
contactsOne per address, keyed by the SHA-256 of the address: confirmation emails sent24 hours, a sliding cap
  • One open signup per address and variant. A setup work runs ensureUnique(["email", "openKey"]) once. openKey holds the variant ID only while the signup is pending or confirmed, and a rule ignores records missing a field, so removing it when a signup ends lets the same address sign up for the next sell-out.
  • Always write `test: context.test`. Real runs filter test: false and test runs test: true, so a test never emails a real shopper and a real run never emails a test signup.
  • Link secrets. A confirmation link carries the signup ID and 32 random bytes; only their SHA-256 is stored. Unsubscribe links are an HMAC-SHA-256 of the signup ID with a BACK_IN_STOCK_LINK_KEY secret, so the dispatcher can build them without storing anything and they never expire. Action tokens don’t fit: they last at most seven days.
  • Documents. Each open signup is one document, and each ended one stays 30 days. Starter’s 2,000 documents hold roughly 1,500 open signups at once after the sends and watches; above that, use Growth or a shorter retention.

Before you build

  • Secrets, added by the merchant in Littleworks → Works → Secrets, never in chat: POSTMARK_SERVER_TOKEN; POSTMARK_SERVER_TOKEN_TEST for test runs; and BACK_IN_STOCK_LINK_KEY, a long random value for unsubscribe links. With Resend, use RESEND_API_KEY instead.
  • At the provider: a verified sender domain, and templates bis-confirm and bis-restock with the fields the works send.
  • Shopify: the included Storefront catalog access reads availability and product details. Add read_products so the admin page can show variant and product titles and images. No order or customer access, and no protected customer data: shoppers type their own address.
  • The endpoints are public, because guests must be able to sign up; Littleworks can’t read a signed-in customer’s email without protected customer data approval. Allowed origins aren’t proof of identity. The double opt-in email is what proves the shopper owns the address.

The works to build

WorkEntry pointBehavior
back_in_stock.setupCallable, run once for realCreate the one-open-signup rule
back_in_stock.subscribePublic HTTP POSTCheck the variant is sold out, create a pending signup, send one confirmation email
back_in_stock.confirmPublic HTTP POSTPending to confirmed from the email link, and make sure the variant is watched
back_in_stock.unsubscribePublic HTTP POSTVerify the signed link and end one signup, or every open signup for the address
back_in_stock.dispatchSchedule, such as */15 * * * *Check watched variants, then claim and send batches, oldest signup first
back_in_stock.restockedShopify Flow (optional)Check the variant is available online and send the first batch
back_in_stock.signups, back_in_stock.statsPage dataThe list (viewPage with search on email and product title), the metrics and the chart
back_in_stock.managePage actionSend again, Cancel alert and Delete, with revisions

A validated example of every work, the page and the theme files is in the Littleworks repository under examples/recipe-back-in-stock. Works can’t call each other: dispatch and restocked share one send module, which CLI users bundle into both and MCP-only agents copy unchanged into each.

Implementation outline for the agent

  1. Accept signups safely. The input schema bounds the variant ID and email and includes an empty website honeypot field, so bots fail validation before a run starts. Check the variant with one Storefront query: missing returns 404, and available returns 409 AVAILABLE so the theme shows Add to cart. Cap confirmation emails at five per address per 24 hours and open signups at 20 per address. Return the same {status: "check_email"} for new, repeated and capped addresses, so the response never reveals who is subscribed.
  2. Claim the confirmation before sending it. Create the pending signup with confirmationsSent: 1 and lastConfirmationAt already set. A concurrent or repeated request hits the uniqueness rule, finds a confirmation sent within ten minutes, and sends nothing. A later resend rotates the link and claims the send in one revision-checked patch. If the provider fails, release the claim and return 502 so the shopper can try again.
  3. Confirm with a click. The confirm work compares the link secret’s hash, creates the variant’s watch if it doesn’t exist (a conflict means it does), then marks the signup confirmed with its revision. A repeated click returns the same result.
  4. Check availability in one query. The dispatcher lists watches that are sending and up to 50 that are waiting, continuing from a saved cursor so more than 50 watched variants are all checked over consecutive runs. One Storefront nodes(ids:) query returns their availability with the titles, image and product URL for the email. Waiting and now available becomes sending; sending and sold out again goes back to waiting.
  5. Claim each batch, then send, then record. Load up to 100 confirmed signups oldest first and fill the request up to 60 KiB, under the 64 KiB request limit; anyone left over goes in the next batch. In one atomic batch, write the claim on the watch (this run, the signup IDs, a send ID) with its revision and create the sends record. A conflict means another run owns the variant: skip it. Then call Postmark’s batchWithTemplates once, and record each signup’s result in batches of 50 with revisions: ErrorCode 0 is notified, 406 an inactive recipient (Not delivered, suppressed), anything else rejected. Clear the claim in the same write as the final send record.
  6. Handle failures without resending. A refused request (4xx or 5xx) sent nothing: everyone stays waiting, and the watch records lastError; a 401 means the key is wrong, and sending stops for the run. A timeout or network error may have sent: those signups become Not delivered with reason unknown, and only staff choose Send again. A claim older than a minute belongs to a run that ended mid-send, and is settled the same way.
  7. Use the run well. Stop starting batches after 6 of the run’s 10 seconds, and send at most five batches per run, about 500 emails. Each batch is one external request, and a run can make 50 external requests. A larger list continues on the next scheduled run, in order.
  8. Build links for the right theme. Email links use the primary domain’s /pages/back-in-stock. In test runs, the works link to the unpublished theme (?preview_theme_id=) so the confirmation page there calls the works’ preview URLs.
  9. Offer the Flow path honestly. back_in_stock.restocked takes variant_id and checks availableForSale again, because stock can arrive at a location that doesn’t sell online, then sends one batch with the same claim. Agents can’t create Flow workflows and shouldn’t guess variable names: tell the merchant the trigger to start from, the condition, the Work name and the Input line variant_id: followed by that trigger’s variant ID variable, checked in the Flow editor. If the trigger offers no previous quantity, skip this path.

See signups and sends in your admin

Build a merchant page named back-in-stock: a table from back_in_stock.signups with email, variantId (variant), productId (product), a status field with the five states, reason, notifiedAt and $createdAt; the six tabs; search on email; and a test filter. Metrics and a bar chart of emails sent per day come from back_in_stock.stats, which leaves test records out. Actions: Send again for Not delivered (back to waiting; a newer signup from the same shopper is reported as already waiting), Cancel alert for pending and waiting, and Delete for privacy requests. Each refreshes the stats.

The page uses one published page slot (Starter has 2). Each view costs two runs, plus one for each tab, page or search change.

Add it to the theme

  • Product-page block. A theme block shown only when the selected variant is sold out. It carries a JSON map of variant availability, follows the theme’s variant picker, sends one Idempotency-Key per submission so a double click is a replay rather than a second run, and maps responses to messages: success or a replayed key is “Check your inbox to confirm”, AVAILABLE is “It’s back”, 400 or 422 is an invalid address, 429 is “try again in a minute” (read the status first; it may not be JSON), and anything else asks to try again. Settings: the subscribe URL, defaulting to the work’s real https://littleworks.app/f/{store}/back_in_stock.subscribe, plus every heading, label, message and the consent text.
  • Confirmation page. A section on template page.back-in-stock reads #confirm=… or #unsubscribe=… from the address, removes it straight away, and shows Confirm alerts, or Stop this alert and Stop all back-in-stock emails, as buttons. Buttons rather than automatic requests keep email link scanners from acting. Settings: the confirm and unsubscribe URLs. The merchant creates a page with handle back-in-stock using the template.
  • Add the primary domain and the .myshopify.com domain to each public work’s origins.

How it uses Littleworks

An example store: 400 signup attempts a month, 300 confirmed, 10 unsubscribes, 20 restocks and about 300 alert emails, with staff opening the page about 100 times.

SourceRuns a month
subscribe, including repeatsabout 440
confirmabout 300
unsubscribeabout 10
dispatch every 15 minutes2,880 (hourly 720, every 30 minutes 1,440, every 10 minutes from 7:00 to 21:59 with */10 7-21 * * * 2,700)
restocked from Flow (optional)about 20
admin pageabout 250
Totalabout 3,900, within Starter’s 10,000
  • Emails go out inside the dispatch runs, so a restock with 1,000 people waiting adds no runs: two scheduled runs of about 500 each.
  • External requests have no monthly allowance; the provider bills its own emails. Rejected input, rate-limited calls and replayed idempotency keys cost no run.
  • The event path would add a run per in-stock inventory change, often thousands a month.

Test before publishing

  1. Deploy every work as a draft and ask the merchant to add the secrets. Test runs use POSTMARK_SERVER_TOKEN_TEST: Postmark’s documented test token accepts sends without delivering them, which suits volume tests. Use one real token and a mailbox the merchant controls for an end-to-end check. With Resend, use its documented test addresses.
  2. Use a published test product with tracked inventory at 0 at every location. Point an unpublished theme at the works’ preview URLs and sign up there. Check read_collection for a pending test record, that one confirmation arrived, that a repeat sends nothing within ten minutes, that the honeypot and an invalid address are rejected without a run, and that an in-stock variant answers AVAILABLE.
  3. Confirm and unsubscribe through the preview theme’s page, and check the signup states and the test-… watch.
  4. Run the dispatcher with invoke_function and trigger: {schedule: {}} (CLI: littleworks invoke back_in_stock.dispatch --schedule). Sold out, it sends nothing. Restock the test product and run it again: the watch becomes sending, emails go out, signups become notified and a sends record appears. A third run sends nothing.
  5. For volume, seed a few hundred confirmed test signups with a temporary callable work and the test token, run the dispatcher twice and check it drains about 500 then the rest, in order. get_run shows each run’s time. Delete the seeding work afterwards.
  6. Test failures: a wrong test key leaves everyone waiting with the error on the watch, and selling out mid-drain returns the watch to waiting.
  7. Run back_in_stock.restocked with {variant_id, flow: {actionRunId: "test-1"}}. Save the page, check its checks, and try each action on test records at the draft page’s manageUrl.
  8. Clean up: restore the product’s inventory, report the test record count and ask before delete_test_records. Leftover open test signups hold the one-open-signup rule against the same address.
  9. Publish when the merchant asks: the public works, then the dispatcher (say “runs every 15 minutes, about 2,880 runs a month”), then the page. Run setup for real, switch the theme settings to the real URLs, and let the merchant publish the theme and add the optional Flow workflow.

Acceptance checks

  • Concurrent signups for the same address and variant create one record and at most one confirmation email. Available or unpublished variants are refused.
  • The signup response is the same for new, repeated and capped addresses, and no address gets more than five confirmations in 24 hours.
  • Unconfirmed signups are never sent a restock email and disappear after 48 hours.
  • A restock seen by Flow and the schedule, or by overlapping runs, emails each confirmed signup once. An interrupted send is never repeated automatically; it shows as Not delivered with reason unknown.
  • A list of 1,000 drains in signup order across runs within every per-run limit. Selling out again stops sending and the rest keep waiting.
  • Every alert’s unsubscribe link works, “Stop all” ends every open signup for the address, and the address can sign up again later. A tampered link is refused.
  • Test runs never email real signups and real runs never email test signups. The page’s counts and chart match the collections.
  • With the dispatcher paused, signups still work. With an invalid key, nothing is lost and the page shows the error.

Building around today’s limits

  • when filters can’t compare with a previous value or with stored data, so restocks are found by the schedule or Flow rather than a filtered inventory event.
  • Public works receive the JSON body only, without request headers. Provider webhooks can’t be signature-checked and mailbox one-click unsubscribe (a form POST) can’t reach a work, so the recipe uses each batch’s per-message results and a page link. For bulk senders, use the provider’s broadcast stream, which handles one-click unsubscribe.
  • Shopify’s privacy requests reach Littleworks, not your collections. Addresses are stored normalized, searchable on the page and deletable with Delete, and ended signups expire.

Make it yours

Cap each wave to the stock that arrived (add unauthenticated_read_product_inventory for quantityAvailable), email VIP customers first, add SMS through another provider with the same claim-and-record steps, or use the transactional email module for other messages. Discount codes, reserving stock and per-market sends are separate builds.