Saturday, 22 August 2026

Building an Automation Hub for a Ticket-Resale Business in .NET

 This is the first post in a series about a project I built for a US-based ticket-resale company: a .NET "automation hub" that connects more than 10 different systems — from POS and marketplaces to banking APIs and an AI that reads email.

The problem

The event-ticket resale business in the US runs on a patchwork of disconnected platforms: SkyBox (VividSeats' POS for inventory/invoices), Lysted (a resale marketplace), Ticket Evolution, TicketUtils, SeatScouts/AutoQ (purchasing-account management), plus banking (Taekus), Slack, Gmail, and Google Sheets. Every day the operations team had to:

  • Reconcile Lysted sales against inventory in SkyBox
  • Record settlement payments from remit CSV files onto individual invoices
  • Read hundreds of marketing/presale emails so no new ticket drop was missed
  • Monitor balances and sweep cash-back into the main account
  • Bulk-configure a fleet of purchasing accounts

All of it repetitive, all of it error-prone when done by hand. My goal: fold everything into one system that runs automatically on a schedule.

Architecture: one console app, many "sub-programs"

Instead of standing up a fleet of separate services, I went with a model that is simple but extremely effective for internal automation: a single .NET console app dispatched by command-line argument, driven by Windows Task Scheduler.

switch (command)
{
    case "LystedSynchToSB":      // Sync Lysted sales → SkyBox
    case "LystedPaymentCSV":     // Reconcile settlement CSV → invoices
    case "EmailAIAlerts":        // AI reads & classifies email
    case "EmailAITraining":      // Fine-tune the email classification model
    case "TaekusWithdrawPersonalAcc": // Auto-sweep cash-back
    case "GetEvoAccount":        // Withdrawal + reporting for Ticket Evolution
    case "AccessGoogleSheets":   // Sync AutoQ ↔ MySQL ↔ Sheets
    // ... ~25 more commands
}

Each command is an independent "sub-program" in the Businesses layer, sharing common infrastructure: configuration via App.config, logging with Serilog (console + file), data access through Entity Framework + MySQL, and a dedicated DAL layer for the heavier queries.

The main building blocks

1. Data sync & reconciliation

A group of jobs pulls Purchases / Invoices / Inventory / Vendors from the SkyBox API into MySQL, with a follow-up "enrichment" pass. The highlight is the settlement reconciliation job: it reads Lysted's remit CSV (via CsvHelper), looks up the matching SkyBox invoice by external reference, skips invoices already marked PAID, and otherwise injects an ACH payment and PUTs it back to the SkyBox API — logging every result to Google Sheets so the finance team can verify.

2. AI that reads email (the fun part)

The team's inbox receives hundreds of emails a day from Ticketmaster, venues, fan clubs, and more. I built a pipeline: IMAP reads new mail → the content (including inline images — multimodal) goes to an LLM → structured JSON comes back (performer, venue, event date, notification type, links, promo codes...) → results are grouped and sent to the team as digest emails. The system supports two interchangeable AI providers (a fine-tuned OpenAI GPT-4o-mini and Google Gemini) behind the same JSON contract. Full details in posts 2 and 3 of this series.

3. Fintech automation

Two jobs move real money: they check balances on Taekus and Ticket Evolution, and when cash-back or spendable balance is available, they automatically create a withdrawal through the API, then audit-log everything to Google Sheets and post a Slack notification. The design principle: fetch → decide → transact → record → notify, with every transaction leaving a trail in at least two places.

4. Slack as a data source

Rather than waiting for a webhook from Lysted (there isn't one), I took advantage of the fact that "tickets sold" emails were already being forwarded into Slack: a bot reads conversations.history, downloads the attachments, parses the HTML with HtmlAgilityPack, and matches the results against SkyBox inventory. "The HTML email is the only API" — post 4 tells that scraping story, including what broke when Lysted changed their template.

Architecture lessons

  • A console app + Task Scheduler beats microservices for internal automation: one artifact to deploy, one place to debug, and adding a job is just adding a case.
  • Google Sheets is the cheapest UI for a non-technical operations team — every job writes its results to Sheets so a human can inspect them.
  • Slack is the central alerting channel: every job ends with a Block Kit message summarizing the outcome.
  • Deliberate human-in-the-loop boundaries: on the refund sheet, for example, the bot fills in only the columns it can extract with confidence and leaves the rest blank for the finance team — the human/machine boundary is written down explicitly in the code.

Coming up in this series:

  1. Classifying event-ticket emails with AI: from prompt engineering to fine-tuning GPT-4o-mini
  2. Defending against LLM output: parsing "dirty" JSON in C#
  3. Syncing Lysted → SkyBox through Slack: when an HTML email is the only API

Tech stack: .NET (C#), Entity Framework, MySQL, Serilog, MailKit/MimeKit, HtmlAgilityPack, CsvHelper, RestSharp, Google Sheets API v4, Slack API, OpenAI API (fine-tuning), Google Gemini API.

No comments:

Post a Comment

Building an Automation Hub for a Ticket-Resale Business in .NET

  This is the first post in a series about a project I built for a US-based ticket-resale company: a .NET "automation hub" that co...