Skip to content

support-ticket ​

A customer support agent flow. It decides how to answer a customer ticket.

The flow routes VIP customers to a human first. Other tickets follow their category. Billing tickets can end in a refund. How-to tickets get a help article when one matches. The agent answers whether the article solved the problem, then the flow closes the ticket or escalates it.

Files ​

FileFunctionJob
main.reinmainEntry point. Routes by VIP status and category.
refund.reinrefundRefunds within the agent limit. Escalates larger refunds.
faq.reinfaqAnswers how-to questions from the help center.
escalate.reinescalateHands the ticket to a human engineer.
refund/tell_customer.reinrefund/tell_customerA template: one sentence for the customer. refund calls it.
message.struct.reinMessageA shared struct. It is the input of both templates.
faq/close_ticket.reinfaq/close_ticketA template: a last message, then close the ticket. faq calls it.

Run it ​

sh
cargo run -p rein -- check examples/rein/support-ticket
cargo run -p rein -- run --root examples/rein/support-ticket

Graph ​

The decision flow, from rein graph support-ticket:

Source ​

package.json ​

json
{
  "name": "support-ticket",
  "version": "0.1.0",
  "description": "Decide how the support agent handles a customer ticket.",
  "license": "MIT",
  "private": true,
  "files": ["**/*.rein", "README.md", "rein.json"],
  "engines": {
    "rein": "^0.1.0"
  }
}

rein.json ​

json
{
  "entry": "main.rein"
}

main.rein ​

rein
interface Ticket {
  /** What category best fits the ticket? */
  category: "billing" | "account" | "how_to";
  /** Is the customer on the VIP plan? */
  vip: boolean;
}

/**
 * Decide how the support agent handles a customer ticket.
 */
export default function main({ category, vip }: Ticket) {
  if (vip) {
    return @./escalate;
  }

  switch (category) {
    case "billing":
      return @./refund;
    case "account":
      return @./escalate;
    case "how_to":
      return @./faq;
  }
}

escalate.rein ​

rein
/**
 * Hand the ticket to a human support engineer.
 */
export default function escalate() {
  return `Assign the ticket to the tier 2 queue. Tell the customer a person
  will reply within one business day. Do not promise a refund or a fix.`;
}

faq.rein ​

rein
/**
 * Answer a how-to question from the help center.
 */
export default async function answerFaq(
  /** Did you find a help article that answers the question? */
  article_found: boolean,
) {
  if (!article_found) {
    return @./escalate;
  }

  /** Did the customer confirm the article solved the problem? */
  const solved: boolean = await request(
    `Reply with the help article link and ask the customer if it solved the
    problem.`,
  );

  if (solved) {
    return @./faq/close_ticket(
      opening=`Glad the help article solved the problem.`,
      next_step=`Reply to this ticket if it comes back.`,
    );
  }

  return @./escalate;
}

faq/close_ticket.rein ​

rein
/**
 * Send the customer a last message, then close the ticket.
 */
export default function closeTicket({ opening, next_step }: @../message) {
  return `Tell the customer: ${opening} ${next_step} Then close the ticket as
  solved.`;
}

message.struct.rein ​

rein
/**
 * One sentence for the customer: what happened, then what comes next.
 */
export default interface Message {
  /** The first part of the sentence, such as the action taken. */
  opening: string;
  /** The next step the customer can expect. */
  next_step: string;
}

refund.rein ​

rein
/**
 * Process a refund request from a billing ticket.
 */
export default function refund(
  /** Is the requested refund above the agent's limit? */
  refund_over_limit: boolean,
) {
  if (refund_over_limit) {
    return @./escalate;
  }

  return @./refund/tell_customer(
    opening=`Refund the amount the customer asked for and confirm it in the
    ticket.`,
    next_step=`Then tell the customer the refund takes 5 business days.`,
  );
}

refund/tell_customer.rein ​

rein
/**
 * Tell the customer what happens next, in one sentence.
 */
export default function tellCustomer({ opening, next_step }: @../message) {
  return `${opening} ${next_step}`;
}