What is cost per ticket, and how do you measure it in Metabase?
Cost per ticket is your fully-loaded support cost divided by tickets handled in the same period. It's the metric that connects the support queue to the profit-and-loss statement — and the one finance will ask about first. Measure it in Metabase by joining a costs table from finance to ticket counts synced from Zendesk, Intercom, Freshdesk, or Kustomer.
fully-loaded support cost ÷ tickets handledper month. Fix the numerator's definition once (salaries + tooling + overhead), segment by channel and tier, and never optimize it without CSAT on the same dashboard.What cost per ticket measures
It measures the unit economics of support: what one resolved customer problem actually costs the business. The numerator is fully-loaded support cost — agent salaries and benefits, team leads and QA, help desk and tooling licenses, plus an overhead allocation. The denominator is tickets handled (or resolved, if you prefer the stricter cost per resolution) in the same period. Trended monthly it shows whether hiring, tooling, and self-service investments are paying off; segmented by channel it shows that a phone ticket can cost five times a chat ticket, which is what makes deflection and channel-steering decisions concrete.
What data does it need?
- A costs table — one row per month per cost line, exported from finance or the budgeting tool. It rarely lives in the help desk, and that's fine: a small manually-loaded table works.
- The tickets table from your help desk sync, with
solved_at(orclosed_at),channel, and a tier or group field. - For channel-level allocation,
handle_minutesper ticket — without it you can only split by ticket count, which undercharges the slow channels.
SQL patterns
Joins a finance costs table to monthly ticket counts.
-- Fully-loaded monthly cost per ticket: support costs ÷ tickets handled.
-- support_costs holds one row per month per cost line (salaries, tooling,
-- overhead allocations) from finance's export.
WITH monthly_costs AS (
SELECT
date_trunc('month', c.cost_month) AS month,
SUM(c.amount) AS support_cost
FROM support_costs c
GROUP BY 1
),
monthly_tickets AS (
SELECT
date_trunc('month', t.solved_at) AS month,
COUNT(*) AS tickets_handled
FROM tickets t
WHERE t.solved_at IS NOT NULL
GROUP BY 1
)
SELECT
c.month,
c.support_cost,
t.tickets_handled,
ROUND(c.support_cost / NULLIF(t.tickets_handled, 0), 2) AS cost_per_ticket
FROM monthly_costs c
JOIN monthly_tickets t USING (month)
ORDER BY c.month;Allocates each month's cost by handle-time share, then divides by that channel's tickets.
-- Cost per ticket by channel, allocating each month's cost by the share
-- of agent handle time that channel consumed.
WITH monthly_costs AS (
SELECT
date_trunc('month', c.cost_month) AS month,
SUM(c.amount) AS support_cost
FROM support_costs c
GROUP BY 1
),
channel_effort AS (
SELECT
date_trunc('month', t.solved_at) AS month,
t.channel,
COUNT(*) AS tickets,
SUM(t.handle_minutes) AS handle_minutes
FROM tickets t
WHERE t.solved_at IS NOT NULL
GROUP BY 1, 2
)
SELECT
e.month,
e.channel,
e.tickets,
ROUND(
c.support_cost
* e.handle_minutes
/ NULLIF(SUM(e.handle_minutes) OVER (PARTITION BY e.month), 0)
/ NULLIF(e.tickets, 0), 2
) AS cost_per_ticket
FROM channel_effort e
JOIN monthly_costs c ON c.month = e.month
ORDER BY e.month, cost_per_ticket DESC;Pitfalls
Where this metric applies
- Zendesk + Metabase — tickets, channels, and agent metrics to join against costs
- Intercom + Metabase — conversation counts and handle-time data by channel
- Freshdesk + Metabase — ticket volume and group-level splits
- Kustomer + Metabase — conversation and team data for tier-level costing