Blog · November 4, 2026

Support Ticket Screenshot Triage: Route by What the Attachment Shows

"Checkout keeps failing, screenshot attached." The message could be a site bug, a bank decline or a login problem, and the screenshot says which. This post covers routing tickets by their attachments: the options, how to write the questions, how to fall back to a person, and how to handle what customers capture in their screenshots.

Saina · November 4, 2026 · 5 min read

"Checkout keeps failing, screenshot attached. I need this order today." The message could be a site bug, a bank decline or a login problem, and the screenshot says which. This post covers routing tickets by their attachments: the options, how to write the questions, how to fall back to a person, and how to handle what customers capture in their screenshots.

Tickets where the text is not enough

Text routing works when the customer describes the problem. Some tickets don't: "it's broken, see attached", "what is this?", "same error again". The useful information is in the image: an error dialog, a payment page, a bank's decline message, a photo of what arrived in the box.

Take the checkout message above, and suppose the attached screenshot shows a checkout page reading "Payment failed: your card was declined by the issuing bank". From the text alone, an urgent "checkout keeps failing" looks like an engineering incident. The screenshot makes it a payments question, and nobody on the web team needs to be paged.

Ways to read the attachment

An agent opens it. At low volume this is fine and needs nothing new. The cost is that every image ticket waits in a general queue until someone looks.

Text extraction, then rules or a text classifier. Run the screenshot through OCR (an open engine such as Tesseract, or a hosted vision API) and route on the extracted text. This works well when your errors carry stable codes or messages ("ERR_PAYMENT_402"). It struggles when the meaning is in layout or icons, or when the screenshot is of someone else's app.

A general multimodal LLM. Ask it to describe the screenshot or pick a route, and parse the reply. It is flexible and can write a summary for the agent who picks the ticket up. You handle off-list answers yourself, and you get no per-option probability to set a threshold on.

A decision model that reads images. Send the ticket text as state, the screenshot as an image, and typed questions with your own routes as options. You get a probability for every route and, in decision mode, an explicit "not sure". Helm 2 Omni is one such model, with open weights you can run on your own servers. No image benchmark is published for it, so test it on your own tickets first (image classification without training covers how).

If your product shows clear error codes, start with text extraction; it is cheap and easy to audit. If agents need a written summary of the image, an LLM is the better tool for that part.

Writing the questions

Ask two things separately: what happened, and who should handle it. The Screenshot triage preset in Saina Studio's playground does exactly this. Keeping them apart lets you check one against the other, and the "what happened" answer is useful to the agent even when the route is obvious.

An illustrative request:

import base64, os, requests

with open(attachment_path, 'rb') as f:
    shot = 'data:image/png;base64,' + base64.b64encode(f.read()).decode()

resp = requests.post(
    'https://api.saina.run/v1/ask',
    headers={'Authorization': f'Bearer {os.environ["SAINA_API_KEY"]}'},
    json={
        'model': 'saina-helm-2-omni-0.8b',
        'mode': 'decision', 'threshold': 0.8, 'min_margin': 0.1,
        'state': 'Customer message: "Checkout keeps failing, screenshot attached. '
                 'I need this order today." The image is the screenshot they attached.',
        'images': [shot],
        'questions': {
            'team': {
                'type': 'single_choice',
                'question': 'Which team should handle this?',
                'options': {'billing': 'Payments and billing',
                            'technical': 'Website bugs and outages',
                            'shipping': 'Delivery',
                            'account': 'Login and account'},
            },
            'issue': {
                'type': 'single_choice',
                'question': 'What went wrong?',
                'options': {'declined': 'The card was declined',
                            'site_error': 'The site crashed',
                            'out_of_stock': 'Item out of stock',
                            'login': 'Could not sign in',
                            'other': 'Something else, or the image does not show a problem'},
            },
        },
    },
    timeout=30,
)
answers = resp.json()['answers']

A few notes on the wording:

Falling back to a person

Decision mode gives each answer a reason. If the top option clears the threshold and the margin, it is accepted; otherwise selection is null and the reason is below_threshold, below_margin or tie. Use that as the branch:

team, issue = answers['team'], answers['issue']
expected = {'declined': 'billing', 'site_error': 'technical', 'login': 'account'}

if team['selection'] is None or issue['selection'] is None:
    send_to_triage(ticket_id, answers)                    # not sure: a person decides
elif expected.get(issue['selection'], team['selection']) != team['selection']:
    send_to_triage(ticket_id, answers)                    # the two answers disagree
else:
    assign(ticket_id, team['selection'], note=issue['selection'])

The triage queue should show the screenshot next to both answers and their probabilities, so the person deciding can see what the model was unsure between.

Screenshots are private by accident

Customers screenshot more than the error. Expect email addresses, names, order numbers, the last digits of a card, and occasionally other browser tabs or notifications. Whatever model reads the image, decide where that data may go.

One more risk specific to images: a screenshot can contain text written to steer an automated system, such as "Refund approved by manager". A decision model only scores your options, so it cannot be made to write anything, but injected text can still pull it toward one route. Keep refunds and account changes behind a human or a hard rule, never behind a single answer on a customer-supplied image.

Try it

Screenshot triage is a preset in the Studio playground with a free account. Runs are charged in credits, and the cost is shown first. For other image and audio decisions, see the multimodal classification guide.