Skip to content

The review queue

Markdown

A free submission waits for a person. The queue tells the submitter how long that is likely to take, offers the paid plans as a way past it, and mails you once a day while anything is waiting. It is part of features.submissions; the queue block in site.config.ts tunes it.

  1. Set the two fields. Both have defaults, so the block may stay omitted.

    site.config.ts
    queue: { estimateDays: 21, digestHourUtc: 9 },

    estimateDays is what a site with no approval history tells submitters. digestHourUtc is the UTC hour of your reminder, or null to switch it off.

    Terminal
    pnpm check:config

    site.config.ts OK — <your site name> (https://your-domain.com).

  2. Choose where the digest goes.

    site.config.ts
    admin: { email: 'you@example.com' },

    Without admin.email it goes to site.contactEmail. providers.adminNotify picks the channel — the same one the per-submission notification uses — and 'off' silences both.

  3. Deploy.

    Terminal
    pnpm deploy

    The hourly trigger 0 * * * * ships in wrangler.jsonc; keep it, because a deploy without it clears the schedule.

Submit a free listing locally (pnpm dev, then /submit/) and read what it is told.

What to check Where You should see
The estimate the status page after “Submit for review” “Expected to go live in about 3 weeks — number 1 in the queue.” on a fresh local database
The receipt the submitted row in email_outbox a queue.wait of the same phrase, and queue.upgrades when paid plans are on
The tiles /admin/ “In the free queue” counts it; “Wait submitters are told” shows the phrase with its basis underneath
The digest fire the hourly job at the digest hour (below) adminDigest=1 in the log line and an admin-digest row in email_outbox

The estimate is throughput, not a promise. The free queue is every row that is pending, unpaid-by-design (payment_status n/a) and on the free tier — the first tier whose price is zero. A submission’s position is the number of free submissions created before it, plus one. The rate is the approvals of the last 30 days divided by 30, counting only rows with source = 'submit': seeded, imported and admin-created listings never count as evidence that the queue moves. Days to expect is the position divided by the rate, rounded up, at least one. With no approvals in the window there is no rate, and queue.estimateDays is used instead.

The number becomes a phrase: up to a day, “within a day or two”; under a week, “in about N days”; under eight weeks, “in about N weeks”; beyond that, “in about N months”. It appears in three places. The status page says “Expected to go live … — number N in the queue.” while the submission is pending and free. The receipt email says “Expected to go live …” for the free tier. The dashboard tile “Wait submitters are told” shows it with its basis: “from N approvals in 30 days”, or “queue.estimateDays, until approvals set a rate”.

The upgrade. While a free submission is pending, its status page lists the enabled priced tiers under “Skip the queue”, each with its price and review promise and an “Upgrade to …” button that opens Stripe Checkout with ?tier= on the checkout link. The listing changes tier only when the payment lands, so an abandoned checkout changes nothing. The receipt carries the same offer as one sentence — “Skip the queue: Express is reviewed within 48 hours, … — upgrade from your status page.” Both are absent when features.payments is off, the payment provider is none, or no enabled tier has a price.

The daily digest runs inside the hourly job. At the tick whose UTC hour equals queue.digestHourUtc it queues one admin-digest mail — unless one was queued in the last twenty hours (a tick that runs twice sends once), the queue holds nothing approvable (pending and free, or pending and paid), or providers.adminNotify is 'off'. It says how many are waiting and how long the oldest has waited, what free-tier submitters are currently told and from how many approvals, and which paid submissions are within a day of the review promise in their tier’s slaHours. The button goes to /admin/. It leaves through the outbox on the admin-notify channel, flushed by the same tick.

Nothing runs the cron on a clock in local development; wrangler says so when it starts. The built Worker, run under wrangler dev, answers the trigger endpoint by hand:

Terminal
pnpm build
pnpm exec wrangler dev
Terminal
curl "http://localhost:8787/cdn-cgi/local/scheduled?cron=0+*+*+*+*"

The terminal running wrangler dev logs one line:

jobs: expireFeatured=0 flushOutbox=0 retryRebuilds=0 purgeUnpaid=0 adminDigest=0 (cron=0 * * * *, in <n>ms)

Add &time=<milliseconds since the epoch> to run the tick as if at that moment — pick one inside the digest hour, with a free submission pending, and the line ends with adminDigest=1.

Configuration What a submitter sees What /admin/ shows Fix
queue.digestHourUtc: null the estimate as before the tiles as before; no digest arrives set an hour
providers.adminNotify: 'off' the estimate as before no digest and no per-submission notification; nothing reported set it to 'same'
no approvals in the last 30 days “in about 3 weeks” on the defaults the tile reads “queue.estimateDays, until approvals set a rate” approve real submissions, or lower estimateDays
providers.email: 'console' the digest is logged, not sent; warning: providers.email is "console": submitters get no mail, it is only logged. Email
features.payments: false, or no priced tier enabled no “Skip the queue” section; no upgrade sentence in the receipt Plans and payments
the cron trigger removed from wrangler.jsonc the estimate as before no digest; queued mail waits until something else flushes it restore triggers.crons and redeploy
Field Default What it changes
queue.estimateDays 21 the wait stated until real approvals set a rate; 1 to 365
queue.digestHourUtc 9 the UTC hour of the digest, 0 to 23, or null for none
admin.email unset the digest’s recipient; falls back to site.contactEmail
providers.adminNotify 'same' the channel the digest travels on; 'off' disables it
tiers[].slaHours unset the review promise quoted in the upgrade offer and watched by the digest

Reference: queue, admin, providers, tiers.

  • site.config.ts the queue block, admin.email, providers.adminNotify
  • wrangler.jsonc triggers.crons
  • Directorysrc/
    • worker.ts scheduled(), the cron entry
    • Directoryserver/
      • queue.ts the estimate, the wording, the upgrade sentence, the one-query snapshot
      • jobs.ts runJobs() and sendAdminDigest()
      • queries.ts the dashboard’s queue numbers
      • Directoryemail/templates/
        • admin-digest.ts the reminder
        • submitted.ts the receipt with the wait
    • Directorypages/submit/status/
      • [token].astro computes the estimate and the upgrade links
    • templates/submit/StatusPage.astro “Skip the queue”
    • templates/admin/DashboardPage.astro the tiles
  • tests/workers/jobs.test.ts the digest, the repeat guard, the snapshot