Skip to content

Review requests

A customer who scored you 9 or 10 in, one short ask out with a link to the review site that needs reviews most.

Kind
Workflow
Price
0.05¢ per ask
Your time
6 minutes
Approval
Runs on its own
Deepens
Retention
Review.request
You, to your agent

Ask every customer who rated us 9 or 10 for a review, spend up to 10¢, and show me the plan first.

curl -X POST "https://api.sb/leaseline.app/reviews/request?mode=try" \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"goal":"Ask every customer who rated us 9 or 10 for a review, spend up to 10¢, and show me the plan first.","budget":{"amount":0.1,"currency":"USD"}}'
npx startup-builder do Review.request --try --data '{"goal":"Ask every customer who rated us 9 or 10 for a review, spend up to 10¢, and show me the plan first.","budget":{"amount":0.1,"currency":"USD"}}'
{
  "name": "do",
  "arguments": {
    "mode": "try",
    "code": "return await $.Review.request({ goal: 'Ask every customer who rated us 9 or 10 for a review, spend up to 10¢, and show me the plan first.', budget: { amount: 0.1, currency: 'USD' } })"
  }
}
import { $ } from 'startup-builder'

const result = await $.try.Review.request({ goal: 'Ask every customer who rated us 9 or 10 for a review, spend up to 10¢, and show me the plan first.', budget: { amount: 0.1, currency: 'USD' } })
Response
{
  "mode": "try",
  "stored": false,
  "estimate": "About 0.05¢ an ask",
  "sample": {
    "customer": "Cedar Hollow Rentals",
    "score": "10",
    "venue": "Capterra",
    "sent": "yes",
    "published": "day 4"
  }
}
Try mode reads, stores nothing and spends nothingAPI referenceIts Nouns

What you get

  • 1 ask · A customer who scored you 9 or 10 in, one short ask out with a link to the review site that needs reviews most. · Review
  • venue · The review site asked for, and why: the fewest reviews of yours.
  • sent · Whether the ask went out, or why not.
  • published · The review’s URL once the site publishes it, mirrored back as Review.published.

Connect once: Google Business Profile. How long: Seconds.

Trigger

It runs when a customer activates with a score of 9 or more.

TypeScript
on.Customer.activated((customer, $) => customer.score >= 9 && $.Review.request({ customer: customer.$id }))

Steps

Your agent does these steps, one row per step; each step’s check is part of its row and its price.

StepVerbReturnsPriceApproval
1. Check the ruleCollection.queryActivated, scored at or above the line, and not asked in 180 daysFreeRuns on its own
2. Pick the review siteCollection.queryThe site with the fewest of your reviews, from the declared tableFreeRuns on its own
3. Send the askMessage.scheduleThe fixed template with the site’s review linkAbout 0.05¢Runs on its own

Calls: Collection.query, then Message.schedule.

About 0.05¢ an ask, each time it runs, capped at 1¢.

Example: give it a goal

Ask every customer who rated us 9 or 10 for a review, spend up to 10¢, and show me the plan first.

The plan it shows runs 3 steps, priced before it spends anything; Run it above shows the result it returns.

When something fails

Each failure, and what happens next
WhenThen
The customer was asked in the last 180 daysIt is skipped and nothing is billed.
A site’s review link changesThe ask goes to the next site in the table and the link is flagged for you.
You call it twice for one customerOne ask per customer and site: it is idempotent.

Each step retries three times, then falls through to the next route; one item's failure lands in skipped with its reason, never failing the call. Before a step would cross maxCost it stops and returns what it has, with a resume link. Each step's idempotency key is the call's key plus the step and the item: leaseline-review-request-1-<step>-<item>.

Change it

AskCompiles to
Ask after their first renewal instead.Review.request
Answer every review that lands, in my voice.Review.answer
Ask the happiest ones for an introduction too.Referral.ask
TypeScript
import { $, workflow } from 'startup-builder'

export default workflow({
  name: 'review-request',
  verb: 'Review.request',
  input: { customer: 'string', minScore: 'integer', venues: 'string[]' },
  trigger: { event: 'Customer.activated' },
  maxCost: 0.01,
  execute: async (ctx, { customer, minScore = 9, venues = ['g2', 'capterra', 'google-business-profile'] }) => {
    const c = await ctx.step('rule', () => $.Collection.query({ from: 'customer', id: customer, with: ['score', 'lastReviewAsk'] }))
    if (c.score < minScore || within(c.lastReviewAsk, '180d')) return { sent: false }
    const venue = fewestReviews(venues)                                             // the declared table
    return ctx.step('send', () => $.Message.schedule({ to: customer, template: 'review-ask', vars: { venue } }))
  },
})

Extend it with extends: 'Review.request' and base(), or compose your own from these steps.

Works with

Plays that order it: customer referrals, Marketplace listings, Review requests, Written references.

It serves Keep and grow customers, and runs in Borrowed, Keep and grow. It deepens Retention.

For agents

For agents: the guide, its approvals, retries and lookups
FactValue
GuideReview.request
Guide approvalnone
GoalAsk every customer who rated us 9 or 10 for a review, spend up to 10¢, and show me the plan first. It may change counts, the order of steps, soft filters and the route a read takes, and it stops early when the goal is met. It never relaxes your words, raises its own budget, answers its own approval, calls a Play or acts outside your standing approval.
Triggeron.Customer.activated((customer, $) => customer.score >= 9 && $.Review.request({ customer: customer.$id }))
RetryThree times with backoff, then the next route; the same idempotency key never charges, sends or posts twice
Idempotency keyleaseline-review-request-1
LookupsA find: input asks unless exactly one record fits
Step
1. Check the rule
2. Pick the review site
3. Send the ask

The amounts live once, on the hub (approvals).

Who does each step, and how it is checked
StepFunctionDoerCheckerAssuranceInputs
1. Check the rulecodeCodeCode checks each result against its declared typemeasured$0.customer, $0.minScore
2. Pick the review sitecodeCodeCode checks each result against its declared typemeasured$0.venues
3. Send the askcodeCodeCode reads the result back from where it landedmeasured$1, $2.venue

The doer is never the checker, and the weakest assurance on the path is the whole call’s.

Under the hood

Under the hood: the Systems, Nouns and Events it touches
KindWhat it touches
SystemsMailboxes
NounsCollection, Message
EventsCollection.queried, Message.scheduled

Reference

Inputs, outputs, its Event and its errors
NameTypeDefaultMeaning
customerCustomerRequiredThe customer to ask.
minScoreinteger9The score they gave, 0 to 10, at or above which they are asked.
venuesVenueId[]['g2', 'capterra', 'google-business-profile']Where your buyers read reviews; the one with the fewest of yours goes first.
cooldownstring'180d'No second ask inside this.
maxCostnumber0.01The most this call spends each time it runs, in USD. It stops before a step would cross it and returns what it has, with a resume link.

Emits Review.requested. Refuses with max-cost before it spends, and with the Data calls and Accounts groups (refusals).

Contents