Automated post-purchase review invitations for Vendure, timed off your order dates. Sends a branded, Trustpilot-style email that links to your free Trustpilot review page — no paid Automatic Feedback Service required — and can show your live star rating pulled from the free Trustpilot API.
Plugin page & pricing: https://huloglobal.com/vendure-plugins/review-requests/
https://www.trustpilot.com/evaluate/<your-domain> (organic Service Reviews,
free). Optionally add a free Trustpilot developer API key and the email shows
your live TrustScore + review count as social proof. The link template is
configurable, so you can point at Google reviews or anywhere else instead.Admin UI:
This registers the Review Requests page (Marketing section of the admin
nav, /admin/extensions/review-requests), the review panel on the order
detail page and the dashboard widget. Open the page, follow the set-up
checklist at the top (SMTP, email, review page, timing, first send), hit
Send test, and switch the channel on.
Since 0.15.2: invitations need
publicBaseUrl(the unsubscribe link); setoptOutSecretto keep unsubscribe links valid across reinstalls (otherwise a per-install secret is generated and stored); the trigger matches orders at or beyond the chosen state within a 45-day window.Since 0.15.3: the email body may be up to 4 MB (the column is widened to MEDIUMTEXT on first boot) and the subject up to 255 characters — larger saves are refused with a
400that says so. Skipped/failed audit rows older than 18 months are pruned monthly on the worker; sent rows are kept.Since 0.16.0: the review button goes through a signed click redirect on
publicBaseUrl, the worker sends a weekly report (Settings → Weekly report), and the admin nav gains a Review Requests entry plus a dashboard widget. Needs@huloglobal/vendure-licence-sdk0.15.1.
evaluate/<your-domain>.| Setting | Default | Notes |
|---|---|---|
| Trigger state | Delivered | Delivered / Payment settled / Shipped |
| Delay (days) | 14 | days after the order date |
| Min order value | 0 | skip low-value orders |
| Cooldown (days) | 120 | don't re-ask the same customer |
| Max per run | 200 | hourly throttle |
| Review link template | Trustpilot evaluate URL | {domain} is substituted |
Weekly report. Every Monday at 08:00 (server-local time) the worker
emails one summary of last week — invitations sent, review links clicked,
orders skipped, failures and opt-outs, each against the previous week — plus
up to three next steps while the set-up checklist is incomplete. It is sent
through the same SMTP as the invitations (SMTP_* env or the smtp option),
so it needs nothing extra; without SMTP nothing is sent and the Settings card
says why. The recipient defaults to the first channel reply-to, then the
licence customer email, then the address given to "Email me before it ends";
change it or switch the report off under Settings → Weekly report, where
Send me a test and Preview show exactly what goes out. Sent once per
install, on the worker, and never more than once a week even with several
worker replicas.
Set-up checklist. The Overview opens with the five things that have to be true before invitations go out: SMTP configured, invitation email saved, business name and review page set, a channel switched on, first invitation sent. Each row links to the right tab; Hide puts it away until an item comes undone, and it disappears on its own once everything is done.
Dashboard widget. Dashboard → Add widget → Review Requests: the last 7 days' invitations sent, review links clicked and opt-outs, the licence notice when a trial or key is about to end, and a link to the page.
Click tracking. The review button in every real invitation goes through
GET /review-requests/r/<token> on your publicBaseUrl — a signed, rate-
limited redirect that records the first click on that invitation and sends the
customer straight on to Trustpilot / Google. Previews and test emails link
directly. The Overview shows Clicked (30d) and the click-through rate.
The licence server receives the same numbers (invitesSent,
reviewLinkClicks, invitesSkipped, invitesFailed, optOuts, last 30
days) through the evaluation client and the daily heartbeat — counts only,
never customer data.
Free tier: configure, preview and test-send. Scheduled sending requires a licence from https://huloglobal.com/vendure-plugins/review-requests/.
Vendure >=3.5 <4. MySQL / MariaDB / PostgreSQL (every statement is checked
against PostgreSQL 17 and MariaDB by the corpus test in tests/). Tables are created on
boot. Sends via your SMTP (SMTP_SERVER / SMTP_PORT / SMTP_USER / SMTP_PASSWORD / SMTP_FROM,
or pass smtp to init()).
AGPL-3.0-or-later — commercial licences available from HULO Global.
Pick monthly or annual on the plugin's admin banner and click Start 14-day free trial (card required, nothing charged until day 15, cancel any time), or choose lifetime — checkout opens in a new tab and the key installs itself within a minute. You can also buy at https://elite.charity/licence/buy/vendure-plugin-review-requests and paste the emailed key into the admin.
All notable changes to @huloglobal/vendure-plugin-review-requests are documented
here. Format: Keep a Changelog; this project
follows semantic versioning.
Retention pass: a weekly report, a set-up checklist, a dashboard widget and
review-link click tracking. One shared table (hulo_plugin_prefs) and one
column (review_log.clickedAt, with an index) are created on boot. Requires
@huloglobal/vendure-licence-sdk 0.15.1.
GET /review-requests/r/<token>, HMAC over the log row id using the same
secret as unsubscribe links) that stamps review_log.clickedAt on the
first click and sends the customer on to the review page. Previews and
test emails link straight to the review page. The Overview gains a
Clicked (30d) tile with the click-through rate next to Sent.GET/POST digest/settings,
POST digest/send-test, GET digest/preview.GET setup): SMTP
reachable, invitation email saved, business name and review page set,
sending switched on, first invitation sent — each with a hint and a link
to the right tab. Hide it once you are done; it comes back if an item
comes undone. The same items feed the weekly report's next steps.GET widget) shows the 7-day
invitations sent, review links clicked and opt-outs with a link to the
page.invitesSent, reviewLinkClicks,
invitesSkipped, invitesFailed and optOuts for the last 30 days are
reported through both the evaluation client and the daily heartbeat, so
renewal and win-back emails can say what the plugin did. Numbers only.GET meta now includes notice (trial ending, key
expiring, evaluation ended), premiumFeatures, recentStats and
buyUrl. The licensed banner shows the amber notice strip when one
applies and reads "Renews automatically — current key valid until …" for
subscriptions; the trial and free banners list what a licence includes
and the 30-day figures so far. Every "needs a licence" failure (402 or
a licence_required / premium-feature body) is worded the same way in
the admin, and 402 bodies carry buyUrl.review_log row as sending before
the email goes out (so the email can carry that row's click link), then
sets sent or failed. logSend returns the row id.evalStats() reports the last 30 days (was all-time invitesSent).licenceKeyProvider), so an install licensed from the admin page or by
the purchase claim no longer reports unlicensed until restart — the
licence server keeps its usage stats after purchase.Optimisation pass — no new features, no new tables. review_template.body
is widened from TEXT to MEDIUMTEXT on MySQL/MariaDB at boot (Postgres TEXT is
already unbounded).
IN (…) queries instead
of three round trips per order. Skip logging is unchanged (still once
per order and reason). A failed lookup aborts the channel run rather
than sending past the exclusion list.GET /stats no longer runs the full
dry-run scan on every open: the pending summary is memoised for 60 s,
shared between concurrent callers, and dropped after a real run, a
manual send, or a config / exclusion / opt-out change.Number.isFinite guard, formatScore).review_template.body is MEDIUMTEXT (16 MB; was 64 KB
— templates with inlined images were silently truncated). Saving now
returns 400 with the size in the message when the subject exceeds 255
characters or the body 4 MB, and a 500 with the database message
instead of a bare "Save failed"; the admin UI shows that message.review_log rows with
status <> 'sent' older than 18 months are deleted in batches of 5 000.
sent rows are kept — dedup and cooldown depend on them.tests/pg-corpus.test.ts, skipped unless
HULO_PG_URL is set) extracts every SQL statement in src/, translates
it with the dialect adapter and runs it against PostgreSQL 17 with
quoted-camelCase stand-ins for the Vendure tables. 43 statements, all
passing.Reliability and security pass — no new features. A review_claim table
and a (status, createdAt) index on review_log are created on boot.
review_claim
before the email goes out, so the hourly cron (worker), "Send due now"
(server) and the order page can no longer send twice for the same order;
a failed send releases the claim for the next run.PaymentSettled/Shipped triggers matched only
orders still in exactly that state; an order that had moved on to
Delivered was skipped. The scan now matches every state at or beyond the
trigger, and the lookback window is 45 days (was 3) so slow deliveries
and worker downtime no longer lose invitations silently.SUM(boolean) replaced with SUM(CASE …); ADD COLUMN IF NOT EXISTS
replaced with an information-schema check (MySQL 8 boots again).List-Unsubscribe headers on every email and a rate limit on the route.
Sending is refused when no publicBaseUrl (hence no unsubscribe link)
is configured.requireTLS on 587 replaces a fresh
transport per message; a failure on one order no longer aborts the whole
run; log values are truncated to their columns.@, timers are cleared on destroy.hulo-global-logo.svg is now the HG wordmark on the HULO black rounded square (the same mark as the huloglobal.com header), with explicit -light and -dark variants alongside the auto-switching default..env edit, no restart. Renewed subscription keys are picked up automatically too. New admin endpoints licence/purchase-link and licence/claim-status.@huloglobal/vendure-licence-sdk ^0.12.0.order.channelId column, but Vendure stores the order↔channel
relation in the order_channels_channel join table — so the hourly scan
failed on every install (the error was caught and logged, and no
invitations were ever sent). The scan, the admin order panel and manual
send now resolve the channel through the join table.dbConnectionOptions use.
Verified against PostgreSQL 17. MySQL/MariaDB installs are unaffected
(byte-identical passthrough).GET /review-requests/customers/search,
GET /review-requests/exclusions/check.POST /review-requests/trustpilot/detect — resolve review link +
business-unit id + live rating from a domain (+ key) in one call.First release.
trustpilot.com/evaluate/<domain> page (organic reviews, no paid AFS). Optional
free Trustpilot API key reads the live TrustScore + review count to show as
social proof in the email. Link template is configurable (Google, etc.).