All apps · 0 apps
ticketarr
Docker app from jivandabeast's Repository
Overview
Readme
View on GitHubticketarr
A tiny, headless Python service that watches an email inbox for AMC A-List and Regal Unlimited reservation / cancellation emails and mirrors them to your movie tracker (Trakt, Ryot, or Yamtrack). Optionally submits a request for the same movie to Jellyseerr/Overseerr ("Seerr") or Ombi.
Support for additional theater chains is designed to drop in as a single new parser module — see AGENTS.md.
No UI. No public endpoints. Just a docker-native background worker.
How it works
- Log into your IMAP inbox and look for messages from any registered
parser's canonical sender addresses. Out of the box:
- AMC A-List —
AMCTheatres@amctheatres.com - Regal Unlimited —
tickets@regaltickets.com(reservations),noreply@regaltickets.com(refunds)
- AMC A-List —
- Classify each message (reservation / cancellation / other).
- Parse the movie title, theater, showtime, and order/booking number. Regal reservation emails encode the real theater + showtime inside an inline JPEG, so ticketarr OCRs the ticket with Tesseract (bundled in the Docker image) and falls back to email-received time only when OCR fails.
- Resolve the movie to a TMDB id.
- Report the movie to the configured tracker with the showtime as the watched-at timestamp. On a cancellation, remove the corresponding history entry.
- If a requester is configured, also submit a movie request.
Order numbers are persisted (/config/state.json) so cancellations can undo
their matching reservation.
Quick start
git clone <this-repo> ticketarr && cd ticketarr
# Option 1: YAML config
cp config.example.yml config/config.yml
$EDITOR config/config.yml
# Option 2: environment variables
cp .env.example .env
$EDITOR .env
# then uncomment `env_file: - .env` in docker-compose.yml
docker compose up -d
docker compose logs -f ticketarr
The healthcheck endpoint listens on http://127.0.0.1:8765/healthz.
Unraid (Community Apps)
ticketarr ships with a Community Apps template
(templates/ticketarr.xml) and a matching
ca_profile.xml so it can be installed directly from
the Apps tab.
Two ways to configure — pick one
- Environment variables. Fill in the IMAP + TMDB + tracker/requester fields on the template and click Apply. Every knob has a matching env var; see the template descriptions.
- YAML file. Leave every field on the template blank, click Apply,
then look in
/mnt/user/appdata/ticketarr/for theconfig.yml.samplethat ticketarr drops on first run. Copy it toconfig.yml, edit it, and restart the container.
If both are used, environment variables win. Empty env-var fields are
treated as "not set" (they do not clobber a real value in
config.yml), so you can safely leave anything you don't care about
blank on the template.
None of the template fields are marked strictly required because either path is valid — but the container will refuse to start and print a clear error listing the missing keys if IMAP host/username/password or a TMDB credential is missing from both sources.
First-run checklist
- Install ticketarr from Community Apps (or point the CA "Install
template from URL" dialog at
https://raw.githubusercontent.com/jivandabeast/ticketarr/main/templates/ticketarr.xml). - Set the Appdata path (default
/mnt/user/appdata/ticketarr). - Either fill in the env-var fields or start the container, then edit
/mnt/user/appdata/ticketarr/config.yml(copied from.sample). - Check the container log — you should see
Loaded configuration (source=…)followed by each integration'sstartup okline. - If you selected Trakt as the tracker, the log will print a
https://trakt.tv/activateURL and a one-time code — visit it in a browser to complete the OAuth device flow. The token is cached in/config/trakt_token.jsonand reused on every restart.
The healthcheck endpoint is on the container's private network at
http://<container>:8765/healthz. There is no WebUI — clicking the
container's icon in Unraid will not open anything useful.
Configuration
ticketarr loads config in this order, with later sources overriding earlier ones:
TICKETARR_CONFIGenv var → path to a YAML file- First existing default:
/config/config.yml,/config/config.yaml,./config.yml,./config.yaml - Environment variables (override anything above)
See config.example.yml and
.env.example for the full list of options.
Required
- IMAP host + credentials for the inbox that receives ticket emails
- TMDB API key (v3) or bearer token (v4 read access token)
- One of the tracker providers (
trakt,ryot,yamtrack) — ornoneif you only want to submit requests
Filtering senders
By default ticketarr polls for messages from every registered parser's
canonical From-addresses (AMCTheatres@amctheatres.com,
tickets@regaltickets.com, noreply@regaltickets.com). If you want to
poll a different / additional set of senders — for example, a friend who
forwards you tickets, or a SimpleLogin / 33mail alias — set an explicit
list.
YAML (imap.sender_filters in config.yml):
imap:
sender_filters:
- AMCTheatres@amctheatres.com
- tickets@regaltickets.com
- noreply@regaltickets.com
- friend@gmail.com
Environment variable (IMAP_SENDER_FILTERS, comma-separated):
IMAP_SENDER_FILTERS="AMCTheatres@amctheatres.com,tickets@regaltickets.com,noreply@regaltickets.com,friend@gmail.com"
Setting this explicitly replaces the default list — include every sender you want, not just the extras. Whitespace around commas is trimmed. An empty value means "fall back to the registered defaults".
Trakt bootstrap
On first run with tracker.provider: trakt, the app performs Trakt's OAuth
Device Flow. It logs a message like:
Trakt authorization required: go to https://trakt.tv/activate and enter code XXXXXXXX
Visit that URL in a browser, sign in, and enter the code. The resulting
tokens are written to trakt.token_path (default /config/trakt_token.json)
and reused on subsequent runs.
Provider notes
| Provider | Auth | Notes |
|---|---|---|
| Trakt.tv | OAuth (device flow), needs client id + secret | Full watched/unwatched support with showtime timestamp |
| Ryot | Username + password (or session token) | GraphQL against /backend/graphql. ticketarr runs the loginUser mutation at startup and uses the returned session token as Authorization: Bearer …. ryot.api_key remains as an advanced fallback for pre-issued session/access-link tokens. Scrobbles via deployBulkMetadataProgressUpdate. |
| Yamtrack | Web-UI username + password | Drives the internal /media_save Django form (Yamtrack has no REST API for setting a specific watched-at). end_date = start_date + TMDB runtime (fallback 120 min). Always-creates a new row per showing so rewatches are preserved. Contract is unofficial — see AGENTS.md. |
| Jellyseerr / Overseerr | X-Api-Key header |
POST /api/v1/request with {"mediaType":"movie","mediaId":<tmdb>, "is4k": <bool>} (opt-in via seerr.request_4k) |
| Ombi | ApiKey header |
POST /api/v1/Request/movie (Ombi has no V2 movie-request endpoint; V2 hits an SPA fallback and returns HTTP 500). |
Repository layout
ticketarr/ Python package
├─ __main__.py entrypoint
├─ app.py orchestrator
├─ config.py YAML + env config loader (pydantic)
├─ imap_monitor.py IMAP polling / message decode (multi-sender)
├─ parsers/ per-chain email parsers (registry-based)
│ ├─ __init__.py dispatcher + REGISTRY
│ ├─ base.py ParsedEmail + EmailParser protocol
│ ├─ util.py shared regex/date helpers
│ ├─ amc.py AMC A-List (ported from Marquee)
│ └─ regal.py Regal Unlimited
├─ state.py JSON state store (dedupe + order → tmdb map)
├─ tmdb.py TMDB search client
└─ integrations/
├─ base.py Tracker / Requester protocols
├─ trakt.py
├─ ryot.py
├─ yamtrack.py
├─ seerr.py
└─ ombi.py
Dockerfile
docker-compose.yml
config.example.yml
.env.example
templates/ticketarr.xml Unraid Community Apps template
ca_profile.xml Unraid CA repository profile
icon.svg used by CA and the README
Attribution
The AMC A-List email parsers in ticketarr/parsers/amc.py
were ported from the JavaScript parsers in
ijoshi129/Marquee
by @ijoshi129. Thank you for the work.
Development
python -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
python -m ticketarr
Running the tests
The parsers are the highest-risk surface, so the test suite is fixture-driven:
drop sanitized real emails into tests/fixtures/<chain>/ as .eml files
and pytest will parametrize the parser assertions over every one.
pytest
Filename conventions and optional <name>.expected.json sidecar assertions
are documented in tests/fixtures/amc/README.md.
The suite runs green even when the fixtures directory is empty, so you can
commit the harness now and add real emails later.
See AGENTS.md for architecture notes and pointers relevant
for future edits.
License
See LICENSE.
Install Ticketarr on Unraid in a few clicks.
Find Ticketarr in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.
Requirements
Categories
Download Statistics
Related apps
Explore more like this
Explore allDetails
jivandabeast/ticketarr:latestRuntime arguments
- Network
bridge- Shell
bash- Privileged
- false
Template configuration
Persistent state (state.json), cached Trakt OAuth token, and optional config.yml. On first run ticketarr drops a `config.yml.sample` here - copy it to `config.yml` if you'd rather configure via YAML than environment variables. Must be writable by the container.
- Target
- /config
- Default
- /mnt/user/appdata/ticketarr
Private HTTP port that serves /healthz for Docker/Unraid healthchecks. Only exposed to the Docker network; no WebUI is served here.
- Target
- 8765
- Default
- 8765
IMAP server hostname (e.g. imap.gmail.com, outlook.office365.com). Required unless set in /config/config.yml.
IMAP TLS port. Defaults to 993 (implicit TLS).
- Default
- 993
IMAP account username (usually your email address). Required unless set in /config/config.yml.
IMAP account password. For Gmail/Google Workspace you MUST use an App Password, not your real password. Required unless set in /config/config.yml.
Mailbox to poll.
- Default
- INBOX
Use implicit TLS on the IMAP connection.
- Default
- true
Comma-separated list of From-addresses to poll. Leave empty to use every registered parser's default senders (AMC + Regal). Set to override, e.g. to add a friend who forwards you tickets.
On first run, skip messages older than this many days. 0 = process every message in the inbox.
- Default
- 7
Fallback polling interval when the server doesn't support IMAP IDLE.
- Default
- 120
Mark processed messages as seen. Set false if you want them to stay unread.
- Default
- true
TMDB v3 API key. Free - request at https://www.themoviedb.org/settings/api. Either this OR TMDB_BEARER_TOKEN is required (or set one of them in /config/config.yml).
TMDB v4 read access token (Bearer). Optional alternative to TMDB_API_KEY.
Which watch tracker to scrobble to. One of: none, trakt, ryot, yamtrack. Leave blank to inherit the value from /config/config.yml (defaults to `none` when unset). See the matching credential variables below.
Where to request the movie so it's available in your library. One of: none, seerr, ombi. Leave blank to inherit from /config/config.yml.
Trakt application Client ID. Create a free app at https://trakt.tv/oauth/applications - Redirect URI must be `urn:ietf:wg:oauth:2.0:oob`. Required only when TRACKER_PROVIDER=trakt.
Trakt application Client Secret. Required only when TRACKER_PROVIDER=trakt.
Base URL of your Ryot instance (e.g. http://ryot:8000). Required only when TRACKER_PROVIDER=ryot, and only if not set in /config/config.yml.
Ryot username. ticketarr logs in via the loginUser GraphQL mutation at startup and uses the returned session token for all subsequent calls. Required only when TRACKER_PROVIDER=ryot (unless RYOT_API_KEY is set instead).
Ryot password. Required only when TRACKER_PROVIDER=ryot (unless RYOT_API_KEY is set instead).
Advanced fallback: a pre-issued Ryot session token (e.g. from a processAccessLink access link). Use instead of RYOT_USERNAME/RYOT_PASSWORD. Most users should leave this blank.
Base URL of your Yamtrack instance (e.g. http://yamtrack:8000). Required only when TRACKER_PROVIDER=yamtrack, and only if not set in /config/config.yml.
Yamtrack web-UI username. ticketarr drives Yamtrack's internal /media_save form (no REST API supports backdated watched-at). Required only when TRACKER_PROVIDER=yamtrack.
Yamtrack web-UI password. Required only when TRACKER_PROVIDER=yamtrack.
Base URL of your Jellyseerr or Overseerr instance (e.g. http://jellyseerr:5055). Required only when REQUESTER_PROVIDER=seerr, and only if not set in /config/config.yml.
Jellyseerr/Overseerr API key. Required only when REQUESTER_PROVIDER=seerr.
Set true to file 4K requests instead of standard quality.
- Default
- false
Base URL of your Ombi instance (e.g. http://ombi:3579). Required only when REQUESTER_PROVIDER=ombi, and only if not set in /config/config.yml.
Ombi API key. Required only when REQUESTER_PROVIDER=ombi.
ISO 639-1 language code sent with Ombi requests.
- Default
- en
Python logging level (DEBUG, INFO, WARNING, ERROR).
- Default
- INFO