Build on Rails

Build a customer support app on Rails

Give your team a helpdesk that lives next to your data: tickets, the customer's whole history, and one-click actions like refund or resend, all inside your Rails app.

250+ paying companies · 2.7M downloads of the avo gem

01

01 · the hand-built version

What building a customer support app by hand actually costs

A support tool is easy to start and hard to finish. The gap between a tickets table and a place your team works all day is where the effort hides.

  • 01

    Context lives in five tabs

    Answering a ticket well means seeing the customer's orders, their subscription, and their past tickets at once. Stitch that together by hand and every agent ends up spelunking through the Rails console or five browser tabs.

  • 02

    Actions are the whole job, and they are all custom

    Refund the last charge, resend the receipt, extend the trial: the value of a support tool is doing things, not just reading tickets. Each of those is a bespoke button with its own confirmation, permissions, and audit trail.

  • 03

    Assignment and status drift without structure

    Who owns this ticket, is it waiting on us or the customer, has it been escalated: without first-class status and assignment, tickets fall through the cracks and nobody can tell you the queue depth.

  • 04

    Every agent has too much power or too little

    You want frontline agents to reply and tag, but only leads to issue refunds or delete accounts. Hand-building that permission matrix is exactly the kind of security-sensitive code you do not want to own.

02

02 · the Avo version

Your models are already the hard part. Avo is the rest.

Avo maps a helpdesk onto the models you already have. A Ticket resource, a Customer resource, and a handful of actions turn your support queue into a place agents work, with the customer's full history on the same screen and privileged actions gated by policy.

The customer's whole history on one screen

Because a Ticket belongs_to a Customer, the Show view can surface their orders, current subscription, and past tickets as has_many panels. Agents get the context to resolve an issue without leaving the page or opening a console.

One-click actions for the things agents actually do

Model Issue refund, Resend receipt, or Extend trial as Avo actions with their own fields and confirmation screens. They run your real service objects, so the support tool does the work instead of just describing it.

Assignment, status, and escalation as first-class fields

A status badge, an assignee belongs_to, and an escalation flag make the queue legible. Filter the Index view to open and assigned to me, or escalated and waiting, and the team always knows what is next.

Canned replies without a rich-text rebuild

Use a trix or markdown field for the reply body and store reusable snippets as records, so agents answer fast without you maintaining a bespoke editor.

Refunds gated by policy, not by hope

A TicketPolicy decides who may run destructive or financial actions. Frontline agents reply and tag, only leads see the refund action. Avo reads the same Pundit policies your app already trusts.

A Ticket resource with customer context, status, and support actions:

class Avo::Resources::Ticket < Avo::BaseResource
  self.title = :subject
  self.includes = [:customer, :assignee]
  self.search = {
    query: -> { query.ransack(subject_cont: params[:q], id_eq: params[:q], m: "or").result(distinct: false) }
  }
  def fields
    field :id, as: :id
    field :subject, as: :text, required: true
    field :status, as: :badge, options: {
      info: "open",
      warning: "pending",
      success: "resolved",
      neutral: "closed"
    }
    field :customer, as: :belongs_to
    field :assignee, as: :belongs_to, name: "Assigned to"
    field :body, as: :trix
    field :messages, as: :has_many
  end
  def actions
    action Avo::Actions::IssueRefund
    action Avo::Actions::ResendReceipt
  end
end
03

03 · the timeline

From first resource to the team running on it

Hour one

Generate a Ticket resource against your existing tickets table and you have a searchable, filterable queue on real data. Add a belongs_to to Customer and each ticket opens with who it is from.

Day one

Put it behind your existing authentication, add a TicketPolicy so only leads can refund, and your agents start clearing the real queue instead of the shared inbox or the spreadsheet.

End of the week

Canned actions like refund, resend, and extend, status and assignment filters, and the customer's order and subscription history are all on the page, and the shared support inbox is retired.

Prefer to prompt?

Hand this to Claude Code, Cursor, or whatever agent you build with, and review what it ships like any other pull request.

Scaffold a brand-new Ruby on Rails helpdesk from scratch and use Avo 4 as the agent-facing UI (https://avohq.io, docs at https://docs.avohq.io/4.0/). This starts from zero: no codebase, no tables.

1. Run rails new with the default stack to create the project, then generate authentication with Rails' built-in bin/rails generate authentication so agents have to sign in. Add the avo gem to the Gemfile, bundle, run bin/rails generate avo:install, and mount Avo at /avo behind that authentication.
2. Work out the data model before touching a migration. Reason from what a real support team needs: an entity for the incoming ticket, the customer it belongs to, the thread of messages exchanged on it, and the people who work the queue. Decide which qualities are lifecycle states worth an enum (open, pending, resolved, closed, plus whether a ticket is escalated) and which deserve their own table. Sketch how these records associate, then write the migrations that follow from that design. The support concepts on this page (tickets, customers, messages, assignment, escalation, canned replies, refunds) are things the schema has to account for, not a column list to transcribe.
3. Generate an Avo resource per model with bin/rails generate avo:resource. On the ticket resource set self.title to the subject, add self.search with a ransack query over subject and id, and set self.includes to eager-load the customer and assignee. Match field types to the columns you chose: badge for status, belongs_to for customer and assignee, has_many for messages, and a trix or markdown field for the reply body. On the customer resource, expose their orders, subscription, and prior tickets as has_many panels so an agent reads the whole history on one screen (https://docs.avohq.io/4.0/resources.html).
4. Turn the verbs of support work into Avo Actions behind confirmation screens: issue a refund, resend a receipt, extend a trial, escalate a ticket, assign it to an agent. Since the app is new, implement each behavior as a model method or a small service object and have the action call into it, so the business logic lives in the model layer and the action stays a thin trigger (https://docs.avohq.io/4.0/actions.html).
5. Add the filters and scopes agents live in (open and assigned to me, escalated and waiting on us) and build a dashboard with the queue metrics a lead watches: open ticket count, tickets by status, and average time to resolve (https://docs.avohq.io/4.0/dashboards.html).
6. Authorization: write Pundit policies where frontline agents reply and tag while only leads see the privileged actions like refunds and account deletion (https://docs.avohq.io/4.0/authorization.html).
7. Seed realistic sample data: a spread of customers, tickets across every status with a few escalated, message threads, and assignments, so every Index, Show, filter, and dashboard has something real to render.
8. Boot the app, sign in, open /avo, open a ticket, confirm the customer's orders and past tickets show on the same screen, and run a refund action end to end.

04 · start today

Stand up a customer support app this week

Avo is per app with unlimited users, so the whole team works from it without the bill scaling with headcount. When you hit an edge Avo doesn't cover, you drop to plain Rails instead of filing a feature request.

More things teams run from their Rails app

Frequently asked questions

Can I build a customer support system with Ruby on Rails?
Yes. A helpdesk is tickets, customers, messages, and assignments, which are ordinary Rails models. Avo turns them into a working support back office with the customer's history and one-click actions on the same screen. It runs inside your Rails app, so ticket and customer data never leaves your database.
How is this different from a hosted helpdesk like Zendesk?
A hosted helpdesk lives outside your app, so it only knows what you sync to it and acting on a ticket means an integration or a tab switch. Avo runs inside your Rails app against your real models, so an agent can see a customer's orders and run a refund on the same screen, with no data leaving your infrastructure and no per-seat bill.
Can agents see a customer's orders and subscription while answering a ticket?
Yes. Because a ticket belongs_to a customer, the Show view can surface their orders, current subscription, and past tickets as related panels. Agents resolve issues with full context instead of switching between tabs or dropping into the Rails console.
How do I stop frontline agents from issuing refunds?
Guard the action with a Pundit policy. A TicketPolicy can let every agent reply and tag while limiting the refund and account-deletion actions to leads. Avo enforces the same policies your Rails app already uses, so the restriction is real, not just a hidden button. See the authorization docs.
Does it handle canned responses and escalations?
Yes. Reply bodies use a rich-text field, reusable snippets are just records you expose, and escalation is a status or flag you can filter on. You get canned replies and an escalation queue without building a text editor or a workflow engine yourself.
How much does it cost per agent?
Nothing per agent. Avo 4 is priced per app with unlimited users, so a two-person team and a fifty-person support floor pay the same. Over 250 paying companies run on Avo, with 2.7M downloads of the avo gem.