92.4% faster to first reply than before Easol

What this meansCut in the typical wait for a first reply — AI alone now, against the old email threads from before Easol. Measured the same way on both sides: customer's first message to the first reply out. Against Easol tickets staff handled alone, AI is a shade slower, not faster. Both numbers behind it are in the charts below. Rounding can shift the last decimal. Checked 2026-08-03T16:10:59Z.

Customers get a first reply in hours, not days

7.6 hours instead of 100.5

Half of AI-only tickets get a first reply within 7.6 hours. Before Easol the middle ticket waited 100.5 hours. Same day instead of four days later. That is 92.4% off.

The fairest before-and-after here

First reply is timed the same way on both sides: customer's first message to the first reply out. Time to close is not, which is why 8.3 hours against 144.6 needs more caveats than this number does.

The gain is against the old inbox, not against staff

On Easol the three groups are close, and AI alone is a shade slower: 7.6 hours, against 6.8 when AI helped and 6.6 for staff only. 3,835 AI-only tickets and 563 from before Easol got a reply.

Summer slowed everything down

The weekly lines are not flat. AI-only first replies sat between 1.6 and 9.7 hours from late February to late April, then peaked at 144.6 hours in the week of 22 June, with time to close peaking at 166.0 hours the same week. Staff-only tickets peaked too, at 81.3 hours in the week of 6 July. Festival season filled the inbox; this is not an AI problem. Even the worst week sits inside the 97.6 to 170.2 hour range that was normal before Easol.

Worth knowing
  • Before and after are different months with different tickets, not a controlled test. Some of the gain is having a proper inbox instead of a shared mailbox.
  • AI alone is a shade slower to first reply than staff-only tickets, so always frame this against before Easol.
  • Only tickets that got a reply count. Threads nobody ever answered are out of every group.
  • The two charts cover different months on different scales, so no single line runs across the switch.
  • The week of 16 February is the migration week, worked in both places at once. Neither side of it is typical.
  • Weeks with under 10 tickets are missing from the lines, not plotted at zero.
Before Easol On Easol

Typical wait for a first reply (hours)

Before Easol 100.5 h Staff only 6.6 h AI helped 6.8 h AI alone 7.6 h

Typical time to close (hours)

Before Easol 144.6 h Staff only 8.3 h AI helped 8.6 h AI alone 8.3 h No reply yet 11.5 h

Tickets that got a reply

Before Easol 563 Staff only 1,032 AI helped 1,324 AI alone 3,835 No reply yet 0

Before Easol: week by week, hours

0h50h100h150h200h250h 5 Jan 261 Feb 26
First replyTime to close

Middle time each week, by the week a ticket came in. Quiet weeks (under 10 tickets) are left out. Before and after cover different months, so they are two charts on two scales, not one line.

On Easol: week by week, hours

0h50h100h150h200h 1 Mar 261 Apr 261 May 261 Jun 261 Jul 26
AI alone — first replyAI alone — time to closeAI helped — first replyAI helped — time to closeStaff only — first replyStaff only — time to close

Middle time each week, by the week a ticket came in. Quiet weeks (under 10 tickets) are left out. Before and after cover different months, so they are two charts on two scales, not one line.

Where the numbers come from

One entry per figure: what it means, the SQL that made it, and when it was last re-checked.

Faster first reply than before Easol first_response_speedup_vs_pre_easol_pct

Cut in the typical wait for a first reply — AI alone now, against the old email threads from before Easol

Measured the same way on both sides: customer's first message to the first reply out. Against Easol tickets staff handled alone, AI is a shade slower, not faster

WITH r AS (
  SELECT
    CASE
      WHEN imported THEN 'pre_easol'
      WHEN ai_resolved THEN 'full_ai'
      WHEN (ai_draft_sent_count > 0 OR booking_action_performed_by_ai_count > 0) THEN 'partial_ai'
      WHEN sent_message_count > 0 THEN 'no_ai'
      ELSE 'no_reply'
    END AS bucket,
    first_response_time_seconds
  FROM reporting.inbox_tickets
  WHERE company_id = $1 AND created_at < $2 AND first_response_time_seconds IS NOT NULL
)
SELECT ROUND((100.0 * (1 -
  (SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY first_response_time_seconds) FROM r WHERE bucket = 'full_ai')
  / (SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY first_response_time_seconds) FROM r WHERE bucket = 'pre_easol')
))::numeric, 1) AS value

Source: app/models/reporting/inbox/ticket.rb:142 (Reporting::Inbox::Ticket::AttributesBuilder#first_response_time_seconds)

Verified: 2026-08-03T16:10:59Z

Typical wait for a first reply (hours) median_first_response_hours_by_bucket

The middle ticket's wait from the customer's message to the first reply, split by who handled them

SELECT
  CASE
    WHEN imported THEN 'pre_easol'
    WHEN ai_resolved THEN 'full_ai'
    WHEN (ai_draft_sent_count > 0 OR booking_action_performed_by_ai_count > 0) THEN 'partial_ai'
    WHEN sent_message_count > 0 THEN 'no_ai'
    ELSE 'no_reply'
  END AS label,
  ROUND(((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY first_response_time_seconds))/3600.0)::numeric, 1) AS value
FROM reporting.inbox_tickets
WHERE company_id = $1 AND created_at < $2 AND first_response_time_seconds IS NOT NULL
GROUP BY 1 ORDER BY 1

Source: app/models/reporting/inbox/ticket.rb:142 (Reporting::Inbox::Ticket::AttributesBuilder#first_response_time_seconds)

Verified: 2026-08-03T16:10:59Z

Typical time to close (hours) median_resolution_hours_by_bucket

The middle ticket's time from first message to close, split by who handled them

Both clocks start at the first message. Easol tickets stop when marked resolved; old imported threads stop at the last email out, so a long email chain reads as one slow ticket

SELECT
  CASE
    WHEN imported THEN 'pre_easol'
    WHEN ai_resolved THEN 'full_ai'
    WHEN (ai_draft_sent_count > 0 OR booking_action_performed_by_ai_count > 0) THEN 'partial_ai'
    WHEN sent_message_count > 0 THEN 'no_ai'
    ELSE 'no_reply'
  END AS label,
  ROUND(((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY resolution_time_seconds))/3600.0)::numeric, 1) AS value
FROM reporting.inbox_tickets
WHERE company_id = $1 AND created_at < $2 AND resolution_time_seconds IS NOT NULL
GROUP BY 1 ORDER BY 1

Source: app/models/reporting/inbox/ticket.rb:148 (Reporting::Inbox::Ticket::AttributesBuilder#resolution_time_seconds)

Verified: 2026-08-03T16:10:59Z

Tickets that got a reply replied_tickets_by_bucket

Tickets with at least one reply sent, split by who handled them

SELECT
  CASE
    WHEN imported THEN 'pre_easol'
    WHEN ai_resolved THEN 'full_ai'
    WHEN (ai_draft_sent_count > 0 OR booking_action_performed_by_ai_count > 0) THEN 'partial_ai'
    WHEN sent_message_count > 0 THEN 'no_ai'
    ELSE 'no_reply'
  END AS label,
  COUNT(*) FILTER (WHERE sent_message_count > 0) AS value
FROM reporting.inbox_tickets
WHERE company_id = $1 AND created_at < $2
GROUP BY 1 ORDER BY 1

Source: app/models/reporting/inbox/ticket.rb:176 (Reporting::Inbox::Ticket#sent_message_count)

Verified: 2026-08-03T16:10:59Z

Before Easol: typical first reply each week (hours) pre_easol_weekly_first_response_hours

The middle ticket's first reply for each week's tickets, before easol only. Weeks with under 10 tickets are left out so a handful cannot swing the line

Weeks are labelled by their Monday, and a ticket counts in the week it arrived

SELECT
  DATE_TRUNC('week', created_at)::date::text AS label,
  ROUND(((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY first_response_time_seconds))/3600.0)::numeric, 1) AS value
FROM reporting.inbox_tickets
WHERE company_id = $1 AND created_at < $2 AND first_response_time_seconds IS NOT NULL
  AND (imported)
GROUP BY 1
HAVING COUNT(*) >= 10
ORDER BY 1

Source: app/models/reporting/inbox/ticket.rb:142 (Reporting::Inbox::Ticket::AttributesBuilder#first_response_time_seconds)

Verified: 2026-08-03T16:10:59Z

Before Easol: typical time to close each week (hours) pre_easol_weekly_resolution_hours

The middle ticket's time to close for each week's tickets, before easol only. Weeks with under 10 tickets are left out so a handful cannot swing the line

Weeks are labelled by their Monday, and a ticket counts in the week it arrived

SELECT
  DATE_TRUNC('week', created_at)::date::text AS label,
  ROUND(((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY resolution_time_seconds))/3600.0)::numeric, 1) AS value
FROM reporting.inbox_tickets
WHERE company_id = $1 AND created_at < $2 AND resolution_time_seconds IS NOT NULL
  AND (imported)
GROUP BY 1
HAVING COUNT(*) >= 10
ORDER BY 1

Source: app/models/reporting/inbox/ticket.rb:148 (Reporting::Inbox::Ticket::AttributesBuilder#resolution_time_seconds)

Verified: 2026-08-03T16:10:59Z

AI alone: typical first reply each week (hours) full_ai_weekly_first_response_hours

The middle ticket's first reply for each week's tickets, ai alone only. Weeks with under 10 tickets are left out so a handful cannot swing the line

Weeks are labelled by their Monday, and a ticket counts in the week it arrived

SELECT
  DATE_TRUNC('week', created_at)::date::text AS label,
  ROUND(((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY first_response_time_seconds))/3600.0)::numeric, 1) AS value
FROM reporting.inbox_tickets
WHERE company_id = $1 AND created_at < $2 AND first_response_time_seconds IS NOT NULL
  AND (NOT imported AND ai_resolved)
GROUP BY 1
HAVING COUNT(*) >= 10
ORDER BY 1

Source: app/models/reporting/inbox/ticket.rb:142 (Reporting::Inbox::Ticket::AttributesBuilder#first_response_time_seconds)

Verified: 2026-08-03T16:10:59Z

AI alone: typical time to close each week (hours) full_ai_weekly_resolution_hours

The middle ticket's time to close for each week's tickets, ai alone only. Weeks with under 10 tickets are left out so a handful cannot swing the line

Weeks are labelled by their Monday, and a ticket counts in the week it arrived

SELECT
  DATE_TRUNC('week', created_at)::date::text AS label,
  ROUND(((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY resolution_time_seconds))/3600.0)::numeric, 1) AS value
FROM reporting.inbox_tickets
WHERE company_id = $1 AND created_at < $2 AND resolution_time_seconds IS NOT NULL
  AND (NOT imported AND ai_resolved)
GROUP BY 1
HAVING COUNT(*) >= 10
ORDER BY 1

Source: app/models/reporting/inbox/ticket.rb:148 (Reporting::Inbox::Ticket::AttributesBuilder#resolution_time_seconds)

Verified: 2026-08-03T16:10:59Z

AI helped: typical first reply each week (hours) partial_ai_weekly_first_response_hours

The middle ticket's first reply for each week's tickets, ai helped only. Weeks with under 10 tickets are left out so a handful cannot swing the line

Weeks are labelled by their Monday, and a ticket counts in the week it arrived

SELECT
  DATE_TRUNC('week', created_at)::date::text AS label,
  ROUND(((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY first_response_time_seconds))/3600.0)::numeric, 1) AS value
FROM reporting.inbox_tickets
WHERE company_id = $1 AND created_at < $2 AND first_response_time_seconds IS NOT NULL
  AND (NOT imported AND NOT ai_resolved AND (ai_draft_sent_count > 0 OR booking_action_performed_by_ai_count > 0))
GROUP BY 1
HAVING COUNT(*) >= 10
ORDER BY 1

Source: app/models/reporting/inbox/ticket.rb:142 (Reporting::Inbox::Ticket::AttributesBuilder#first_response_time_seconds)

Verified: 2026-08-03T16:10:59Z

AI helped: typical time to close each week (hours) partial_ai_weekly_resolution_hours

The middle ticket's time to close for each week's tickets, ai helped only. Weeks with under 10 tickets are left out so a handful cannot swing the line

Weeks are labelled by their Monday, and a ticket counts in the week it arrived

SELECT
  DATE_TRUNC('week', created_at)::date::text AS label,
  ROUND(((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY resolution_time_seconds))/3600.0)::numeric, 1) AS value
FROM reporting.inbox_tickets
WHERE company_id = $1 AND created_at < $2 AND resolution_time_seconds IS NOT NULL
  AND (NOT imported AND NOT ai_resolved AND (ai_draft_sent_count > 0 OR booking_action_performed_by_ai_count > 0))
GROUP BY 1
HAVING COUNT(*) >= 10
ORDER BY 1

Source: app/models/reporting/inbox/ticket.rb:148 (Reporting::Inbox::Ticket::AttributesBuilder#resolution_time_seconds)

Verified: 2026-08-03T16:10:59Z

Staff only: typical first reply each week (hours) no_ai_weekly_first_response_hours

The middle ticket's first reply for each week's tickets, staff only only. Weeks with under 10 tickets are left out so a handful cannot swing the line

Weeks are labelled by their Monday, and a ticket counts in the week it arrived

SELECT
  DATE_TRUNC('week', created_at)::date::text AS label,
  ROUND(((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY first_response_time_seconds))/3600.0)::numeric, 1) AS value
FROM reporting.inbox_tickets
WHERE company_id = $1 AND created_at < $2 AND first_response_time_seconds IS NOT NULL
  AND (NOT imported AND NOT ai_resolved AND ai_draft_sent_count = 0 AND booking_action_performed_by_ai_count = 0 AND sent_message_count > 0)
GROUP BY 1
HAVING COUNT(*) >= 10
ORDER BY 1

Source: app/models/reporting/inbox/ticket.rb:142 (Reporting::Inbox::Ticket::AttributesBuilder#first_response_time_seconds)

Verified: 2026-08-03T16:10:59Z

Staff only: typical time to close each week (hours) no_ai_weekly_resolution_hours

The middle ticket's time to close for each week's tickets, staff only only. Weeks with under 10 tickets are left out so a handful cannot swing the line

Weeks are labelled by their Monday, and a ticket counts in the week it arrived

SELECT
  DATE_TRUNC('week', created_at)::date::text AS label,
  ROUND(((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY resolution_time_seconds))/3600.0)::numeric, 1) AS value
FROM reporting.inbox_tickets
WHERE company_id = $1 AND created_at < $2 AND resolution_time_seconds IS NOT NULL
  AND (NOT imported AND NOT ai_resolved AND ai_draft_sent_count = 0 AND booking_action_performed_by_ai_count = 0 AND sent_message_count > 0)
GROUP BY 1
HAVING COUNT(*) >= 10
ORDER BY 1

Source: app/models/reporting/inbox/ticket.rb:148 (Reporting::Inbox::Ticket::AttributesBuilder#resolution_time_seconds)

Verified: 2026-08-03T16:10:59Z