← Blog
Guides7 min readThe Utter team2 viewsUpdated

Turn a request form into a triaged issue automatically (no human inbox in the middle)

XLinkedIn

Requests do not stall because nobody wants to do the work. They stall because they land in an inbox, and an inbox is a queue with no owner. A bug report from a customer, an access request from another team, a "can you look at this" from sales, all pile into someone's email, and that person becomes the bottleneck.

They forward, they copy-paste into the tracker, they set a priority they are guessing at. The request waits for them to have a free half hour.

The fix is not a better inbox. It is deleting the inbox from the path. A public form should create a real, tracked, already-classified issue the moment someone hits submit, with no human relay in between. Here is how that flow works in Utter, and, honestly, where a person still has to stay in the loop.

The path, end to end

An intake form in Utter lives at a public URL:

https://utter.ae/f/<token>

You share the token, external submitters do not need an account, and you never expose the workspace. When someone submits, four things happen in order:

  1. An issue is created in the project the form is attached to.
  2. The form's routing defaults are stamped onto that issue.
  3. The workspace's issue.created automations run against it.
  4. If AI triage is on for that project, it takes a first pass at priority and labels.

The same path, drawn:

flowchart TD
    S["External submitter fills the public form"] --> I["Issue created in the form's project"]
    I --> RD["Routing defaults stamped: assignee, labels, sprint"]
    RD --> AU["issue.created automations run"]
    AU --> AI["AI triage takes a first pass, if enabled"]
    AI --> B["A classified, owned ticket on the board"]

By the time you look at the board, the request is a ticket that already has a home, an owner, and a rough classification. Nobody triaged it by hand. Let me walk each step.

Step one: the form becomes an issue

You build the form in the project's Forms tab. Fields come in the usual kinds: short and long text, email, number, a rating, single and multi select, date, file upload, and a few more. Each answer either maps onto an issue field or gets composed into the issue body, so the ticket reads like something a teammate wrote, not a raw form dump.

Building an intake form in the Forms tab, with field kinds on the left and the live preview on the right

File uploads work too. An external submitter can attach a screenshot or a log, and it lands as a real attachment on the created issue. Those bytes count against your workspace storage the same as any other upload, which is worth knowing before you point a busy public form at a Free workspace.

One deliberate limit: a form enforces one submission per email. That keeps a shared link from turning into a hundred duplicate tickets from the same person hammering submit. If you need repeat submissions from the same address, a form is the wrong tool.

Step two: routing defaults do the boring sorting

Every form carries a set of routing defaults, and this is where most of the "who deals with this" question gets answered without anyone reading the request. A form can set:

  • the assignee, so the ticket lands on a specific person's plate
  • one or more labels, so it is tagged on arrival
  • a sprint, if you want intake to flow into current work
  • a starting status column, so it drops into "Triage" or "Needs review" instead of the default

These are applied at creation. A "Report a bug" form can label everything bug and drop it in the QA lead's queue. A "Request access" form can assign the IT owner and start the ticket in a review column. The routing is per form, so you can run several forms into the same project with different defaults and let the form itself do the first sort.

Utter checks that each routing id actually belongs to the project before it applies it. If you point a form at a label or assignee that later gets removed, the submission still creates the issue, it just skips the stale default rather than failing. That matters for a public form you are not watching every day.

Step three: automations catch what a static default cannot

Routing defaults are fixed. Automations are conditional, and that is the difference. When the issue is created, Utter fires the workspace's issue.created automation rules against it, the same rules you would write for any issue, now reaching form intake too.

An automation is a plain "when this, if that, then these" rule. On issue.created you can match on the issue's project, type, title text, or existing labels, and then act: set a priority, reassign, add a label, move status.

flowchart LR
    T["issue.created fires"] --> C{"title contains outage?"}
    C -->|yes| A["raise priority and assign the on call owner"]
    C -->|no| D["keep the form defaults"]

So a form default might tag everything support, and an automation can then look at the title, and if it contains "outage" or "down", bump the priority and route it to the on-call assignee. The static default handles the common case; the rule handles the ones that need to jump the queue.

The automations list for a project, each rule showing its trigger, conditions, and actions

This runs best-effort and never blocks the submission. If an automation fails, the submitter still gets their confirmation and the issue still exists. You are not trading reliability for cleverness.

Step four: AI triage takes the first guess

The last layer is the one people expect to be magic and should treat as a first draft. Turn on AI triage for a project, and a newly created issue gets read by the model, which proposes a priority and labels from that project's real palette. It does not invent a label that does not exist; anything off-list is dropped. Enabled per project, it can apply that first pass on its own so the ticket is classified before a human ever opens it.

There is also a suggestion-only version on the issue page for the cases you want to keep a hand on. A person clicks, the model suggests priority and labels with a short reason, and the person applies it or ignores it. Same engine, human in the loop. For a high-volume support form, autonomous triage saves the drudgery. For anything where a wrong priority is expensive, the manual suggestion is the safer default.

The AI triage suggestions modal, one row per issue with a suggested priority, label chips, and a short reason

The honest caveat: AI triage is a classifier, not a judge. It is good at "this is probably a bug, probably medium, probably about billing." It is not good at knowing that this particular customer is about to churn, or that this "small" request touches a system mid-migration. Treat its output as a starting label you can override, because you will need to.

What still needs a person

The pitch is "no human inbox in the middle," and that is true for the routing. It is not true for the judgment. Here is what does not automate away.

Someone still decides the routing rules exist. The form defaults and the automations are your judgment, encoded once, applied many times. That is the point, but it is still your call, and a badly aimed rule quietly misroutes every submission until you notice.

Someone still owns the reply. A ticket landing in the right column is not an answer to the person who filed it. Utter can send the submitter a confirmation and notify the form owner, but the actual "we are looking at this, here is when" is a human writing to a human.

And someone still reviews the edges. The duplicate that slipped through, the request that is really three requests, the angry one that needs a person now. Automation clears the routine 80 percent so your attention lands on the 20 percent that actually needs it. That is the win. It is not the same as the work disappearing.

Setting it up

The short version: create a form in your project's Forms tab, set its routing defaults, write one issue.created automation for the exception you care about, and flip on AI triage if the volume justifies it. Start with the routing defaults alone, watch what lands for a week, then add the automation once you can see the pattern.

The Forms tab listing a project's intake forms with their share links

If you want the full picture of how the intake and automation pieces fit together, the forms and automation docs cover the field kinds and rule options.

Picking submissions up over the API

If you would rather have an agent read and file submissions instead of a rule, the same issues sit behind the REST API, so a scoped agent can pick them up the moment they are created. Form-created issues are ordinary issues, so a key with the issues:read scope can list them, filtered by the label your routing defaults stamp on:

curl "https://utter.ae/api/v1/workspaces/acme/projects/WEB/issues?label=bug" \
  -H "Authorization: Bearer $UTTER_API_KEY"
const res = await fetch(
  "https://utter.ae/api/v1/workspaces/acme/projects/WEB/issues?label=bug",
  { headers: { Authorization: `Bearer ${process.env.UTTER_API_KEY}` } },
);
const { data } = await res.json();
for (const issue of data) {
  console.log(issue.key, issue.priority, issue.title);
}
import os
import requests

res = requests.get(
    "https://utter.ae/api/v1/workspaces/acme/projects/WEB/issues",
    params={"label": "bug"},
    headers={"Authorization": f"Bearer {os.environ['UTTER_API_KEY']}"},
)
for issue in res.json()["data"]:
    print(issue["key"], issue["priority"], issue["title"])

The list is cursor-paginated; the response carries a pagination.next_cursor you pass back as ?cursor= when a busy form outgrows one page. From there the agent can do whatever your rules cannot: dedupe against existing tickets, ask the submitter a follow-up, or file the request under the epic it actually belongs to.

Frequently asked questions

How do you turn a request form into a triaged issue automatically?

Attach a public intake form to a project so every submission creates a real issue, stamp the form's routing defaults (assignee, labels, sprint, starting status) onto it, run your issue.created automation rules against it, and let AI triage take a first pass at priority and labels. In Utter that whole path runs with no human inbox in the middle.

Do external submitters need an account to use the form?

No. An intake form lives at a public /f/<token> URL, so submitters never need an account and you never expose the workspace. One deliberate limit: a form enforces one submission per email, so a shared link cannot turn into a hundred duplicate tickets from the same person.

What is the difference between routing defaults and automations?

Routing defaults are fixed values the form stamps on every issue at creation: the assignee, one or more labels, a sprint, and a starting status column. Automations are conditional "when this, if that, then these" rules that fire on issue.created, so a title containing "outage" can get a priority bump and the on-call assignee while the defaults handle the common case. They run best-effort and never block the submission.

Can AI triage classify form submissions on its own?

Yes, per project: AI triage proposes a priority and labels from that project's real palette, drops anything off-list, and can apply the first pass autonomously so the ticket is classified before a human opens it. Treat it as a first draft: it is a classifier, not a judge, and knowing that a "small" request touches a system mid-migration is still on you.

Related reading

Add a comment

Start the conversation.