Display remains capped
Only the three newest episode rows appear on the card.
A richer walkthrough for installing from Unraid Apps or QNAP Container Station, testing, scheduling, backing up, and safely operating TautWeekly for Plex beside Tautulli—without installing anything inside Tautulli.
$ ./tautweekly.sh verify [OK] Docker daemon available [OK] Compose command detected [OK] Tautulli API connected [OK] Persistent data writable [OK] Six preview states supported [OK] Weekly schedule syntax valid NEXT: ./tautweekly.sh preview-all No real Plex user email will be sent. $ |
ZIP and TAR.GZ contain the same application.
Keep both as backups if useful, but install only one into the final QNAP folder. They are two archive formats—not two editions.
Best when extracting on Windows or with a graphical archive utility. After copying to QNAP, mark the shell scripts executable.
TautWeekly-nas-docker.zip
Best when extracting directly on QNAP/Linux over SSH. It is the more native Unix format and better preserves executable permissions.
TautWeekly-nas-docker.tar.gz
| Workflow | Use | Extra step |
|---|---|---|
| Extract on Windows, then copy to QNAP | ZIP | chmod +x qnap-install.sh tautweekly.sh |
| Extract directly on QNAP over SSH | TAR.GZ | Permission repair usually unnecessary, but safe to run |
mkdir -p /share/Container/tautweekly tar -xzf TautWeekly-nas-docker.tar.gz \ --strip-components=1 \ -C /share/Container/tautweekly
TautWeekly for Plex communicates through APIs and network access.
Tautulli upgrades cannot remove TautWeekly for Plex files.
./data keeps configuration, state, logs, and previews.
Microsoft’s cross-platform runtime powers the newsletter engine.
No Windows Task Scheduler or permanent QNAP cron edits.
Installing TautWeekly for Plex inside Tautulli couples upgrades, backups, and troubleshooting. A dedicated container is safer and easier to restore.
The installer checks most requirements automatically.
/share/Container/tautweekly| Port | Purpose |
|---|---|
| 8181 | Tautulli host port |
| 8787 | LAN preview server |
| 587 | Typical SMTP STARTTLS |
Every risky action stays behind preview and TestEmail checkpoints.
Confirm Docker is available before beginning.
/share/Container/tautweekly
cd /share/Container/tautweekly chmod +x qnap-install.sh tautweekly.sh
./qnap-install.sh
The installer creates .env, pulls the published image, starts the service, runs setup, restarts, and verifies.
For Arizona, use TZ=America/Phoenix. Confirm PREVIEW_BASE_URL contains the NAS LAN IP.
./tautweekly.sh verify ./tautweekly.sh list-users ./tautweekly.sh exclude-users ./tautweekly.sh preview-all ./tautweekly.sh send-test-all ./tautweekly.sh schedule-status
./tautweekly.sh schedule-enable
The container can run, preview, and send to TestEmail without being authorized to email all Plex users.
The official v2 template pulls the published amd64/arm64 image and applies safe Unraid appdata, identity, port, and timezone defaults.
Review /mnt/user/appdata/tautweekly, port 8787, timezone, PUID 99, and PGID 100. The template pulls ghcr.io/sparkmoxie/tautweekly:latest.
Run pwsh -NoLogo -NoProfile -File /opt/tautweekly/Setup-First.ps1, then run Verify-Setup.ps1. Setup offers the live exclusion roster; later, run Manage-User-Exclusions.ps1 to revise it independently. Scheduling remains disabled until explicitly enabled. The manual Compose steps below remain available for custom stacks.
mkdir -p /mnt/user/appdata/tautweekly cd /mnt/user/appdata/tautweekly
Extract either archive here so compose.yaml, Dockerfile, tautweekly.sh, app/, and data/ are all in this directory.
cp .env.example .env chmod +x tautweekly.sh nano .env
COMPOSE_PROJECT_NAME=tautweekly TZ=America/Phoenix PUID=99 PGID=100 UMASK=077 PREVIEW_BIND=0.0.0.0 PREVIEW_PORT=8787 PREVIEW_BASE_URL=http://UNRAID_SERVER_IP:8787
America/Phoenix is an example. Replace it with the correct timezone and replace UNRAID_SERVER_IP with the LAN address of the Unraid host.
Compose Manager Point the stack/project at /mnt/user/appdata/tautweekly/compose.yaml, review the environment values, then bring the stack up.
docker compose pull docker compose up -d
./tautweekly.sh setup ./tautweekly.sh verify ./tautweekly.sh status
The simplest route is the Unraid host LAN IP and Tautulli’s published host port:
"TautulliUrl": "http://UNRAID_SERVER_IP:8181"
When both services share the same custom Docker network, the Tautulli container name can be used instead:
"TautulliUrl": "http://tautulli:8181"
./tautweekly.sh list-users ./tautweekly.sh exclude-users ./tautweekly.sh preview-all ./tautweekly.sh send-test-all ./tautweekly.sh schedule-status
Open the preview index at http://UNRAID_SERVER_IP:8787/preview-all/preview-all-00-INDEX.html.
./tautweekly.sh schedule-enable
The included relative volume ./data:/data resolves to /mnt/user/appdata/tautweekly/data when the Compose project lives in the recommended folder.
Use http://[IP]:[PORT:8787]/preview-all/preview-all-00-INDEX.html as the preview index URL after the official template is installed.
ca_profile.xml and templates/tautweekly.xml live at the repository root and are validated in CI before release.
Avoid the most common container networking mistake.
Inside TautWeekly for Plex, localhost means TautWeekly for Plex—not the QNAP host or the separate Tautulli container.
Use the same host address another LAN device uses to open Tautulli.
"TautulliUrl": "http://media.example.test:8181"
Attach both containers to a user-defined network and resolve Tautulli by service name.
"TautulliUrl": "http://tautulli:8181"
PlexServerUrl and PlexToken improve clearLogo support. Tautulli and text-title fallbacks remain available.
The wizard writes live settings into persistent storage.
URL, API key, history window, watched thresholds, and exclusions.
Sender, Reply-To, SMTP host/port, authentication, and TestEmail.
Server label, footer name, Plex web link, and optional direct Plex metadata.
Day, local time, grace period, polling, and enabled state.
Ten-second production recipient delay and shorter test delay.
Setup selects excluded users by stable Tautulli ID; the standalone command revises them without changing SMTP or scheduling.
{
"TautulliUrl": "http://media.example.test:8181",
"ApiKey": "PASTE_YOUR_TAUTULLI_API_KEY_HERE",
"FooterServerName": "My Plex",
"FromName": "My Plex Newsletter",
"SmtpHost": "smtp.example.com",
"SmtpPort": 587,
"SmtpEnableSsl": true,
"TestEmail": "you@example.com",
"ScheduleDay": "Friday",
"ScheduleTime": "09:30",
"SendDelaySeconds": 10,
"ScheduleEnabled": false
}The live config is stored under data/, receives restrictive permissions, and is not included in either distributed archive.
Run ./tautweekly.sh exclude-users, choose comma-separated rows or ranges, press Enter to keep the current list, or type none to clear it. Treat displayed names and emails as private.
Every routine action is exposed through ./tautweekly.sh.
| Command | What it does | Risk |
|---|---|---|
verify | Checks runtime, assets, config, Tautulli, SMTP reachability, and schedule syntax. | Safe |
list-users | Lists selectable Plex/Tautulli users. | Safe |
exclude-users | Updates stable user exclusions without rerunning setup. | Config only |
preview [user] | Creates one browser preview. | No email |
preview-all [user] | Creates six HTML states and an index. | No email |
send-test [user] | Sends one message only to TestEmail. | TestEmail |
send-test-all [user] | Sends all six only to TestEmail. | TestEmail |
welcome [user] | Sends a real welcome and records state. | Real recipient |
send-all | Sends to every eligible Plex user after confirmation. | Bulk send |
roster | Displays access/welcome state. | Safe |
repair-assets | Repairs email-safe assets. | Safe |
logs / status | Shows logs or service state. | Safe |
restart / update | Restarts or pulls the published image without deleting data. | Service change |
backup | Archives persistent data. | Safe |
shell | Opens a container shell. | Advanced |
All states use the production renderer and MIME/CID pipeline.
One-off onboarding without weekly stats, quiet copy, or warm-up copy.
WELCOMEONLY = TRUEFirst scheduled delivery with onboarding replacing empty statistics.
RECENTACCESS + ZERO STATSFirst scheduled delivery with populated statistics.
RECENTACCESS + ACTIVITYPopulated established newsletter; sample stats only when needed for layout testing.
NORMAL ACTIVITYZero activity after warm-up, showing QUIET IN THIS SECTOR.
WARMINGUP = FALSEZero activity during the initial window, showing STATS ARE WARMING UP.
WARMINGUP = TRUE./tautweekly.sh preview-all
http://QNAP_LAN_IP:8787/preview-all/preview-all-00-INDEX.html
send-test-all sends six real SMTP messages, but every message goes exclusively to TestEmail.
The same card renderer is used by browser previews, TestEmail, welcomes, normal newsletters, and quiet-week Latest Releases.
TV cards show the three newest episode rows. Totals above three add a gold footer at the bottom: 1 additional episode recently added for four total, or X additional episodes recently added for five or more. Totals from one through three show no footer.
Movie cards show up to two genres directly below the title. More than two becomes Genre One, Genre Two, and more. The 13px / 500-weight line uses the same muted color as the description and disappears entirely when no genre metadata is available.
Movie content height was expanded for the genre row, while TV details reserve room for three episode lines and a bottom-aligned overflow footer.
The scheduler records attempts to avoid duplicates.
ScheduleEnabled=false until explicitly enabled.
Day and time follow TZ in .env.
A short restart after send time can still be handled.
The attempt is recorded before delivery starts.
./tautweekly.sh schedule-status ./tautweekly.sh schedule-enable ./tautweekly.sh schedule-disable ./tautweekly.sh schedule-reset
It clears today’s attempt guard. A later scheduler poll may permit another real SendAll on the same day.
The data folder is the installation’s durable identity.
config.json: API, SMTP, branding, schedulestate.json: warm-up trackingaccess-state.json: new-user and welcome historyscheduler-state.json: same-day send guard./tautweekly.sh backup
./tautweekly.sh update
The backup includes config.json. Treat the generated archive as a private credential-bearing file.
The package minimizes exposure; the administrator controls LAN and credentials.
UMASK=077 restricts new files.env or config.jsonStart with verification and logs.
./tautweekly.sh status and ./tautweekly.sh logs. Confirm port 8787 and PREVIEW_BASE_URL use the current NAS LAN IP.chmod +x qnap-install.sh tautweekly.sh. This is common after Windows ZIP extraction.The shortest safe path from extraction to scheduled delivery.
cd /share/Container/tautweekly chmod +x qnap-install.sh tautweekly.sh ./qnap-install.sh ./tautweekly.sh verify ./tautweekly.sh list-users ./tautweekly.sh exclude-users ./tautweekly.sh preview-all ./tautweekly.sh send-test-all ./tautweekly.sh schedule-status # Enable only after approving all six TestEmail messages: ./tautweekly.sh schedule-enable
Archive integrity, shell syntax, Compose structure, Linux-safe paths, non-root startup, and six-state wiring.
Your QNAP, Docker daemon, Tautulli, Plex metadata, LAN, SMTP provider, and mail clients still require acceptance testing.
Enabling the schedule authorizes confirmation-free weekly SendAll at the configured local time.
Do not enable the schedule until the browser preview index and all six TestEmail messages are approved on desktop and mobile clients.
When Tautulli reports one show or season row, TautWeekly for Plex inspects child episode timestamps inside the newsletter window and calculates the true remainder beyond the three visible rows.
Only the three newest episode rows appear on the card.
Four total produces 1 additional episode recently added; five or more uses the exact remaining count.
A wrapped title or IMDb row no longer pushes the gold overflow line outside the visible card.
The muted 13px, weight-500 line stays directly under the movie title with the first two genres and “, and more” when applicable.
Movie genres now appear immediately beneath the title identity on regular cards and every movie-based hero layout.
The title and genre use normal flow, eliminating the large blank gap caused by fixed-height table distribution.
The genre appears below either the clearLogo or the normal fallback title.
The genre appears below the normal title while the mobile banner remains clearLogo-free.
13px, weight 500, muted description color, first two genres plus “, and more,” and no blank row when genre metadata is missing.
The container runs in Linux-container mode; the included PowerShell wrapper removes the need for Git Bash or WSL.
Copy .env.example to .env. Use PREVIEW_BASE_URL=http://localhost:8787 and an IANA timezone such as America/Phoenix.
Run docker compose up -d --build, then .\tautweekly-docker.ps1 setup.
Run verify, preview-all, and send-test-all before enabling the schedule.
Copy-Item .env.example .env docker compose pull docker compose up -d .\tautweekly-docker.ps1 setup .\tautweekly-docker.ps1 verify .\tautweekly-docker.ps1 preview-all .\tautweekly-docker.ps1 send-test-all
Use http://host.docker.internal:8181 on Docker Desktop, or use http://tautulli:8181 when both services share a custom Docker network.
The reported symptoms came from three independent defects, all corrected in this release.
Pressing Enter now safely stores an empty Plex token and continues setup.
Frame counts are newline-delimited before parsing, so valid animated files are no longer mislabeled as unreadable.
Preview HTML uses assets/..., backed by a real /data/output/assets mirror inside the Python web root.
Container startup removes the old workaround symlink when present, creates a real directory, and refreshes it from /data/assets.
docker compose up -d --build ./tautweekly.sh repair-assets ./tautweekly.sh verify ./tautweekly.sh preview-all
Light viewing weeks reveal more detail without making heavy viewing weeks excessively tall.
Mini poster, movie title, Rotten Tomatoes critic score, and audience score.
Mini show poster, show title, Sxx EPxx episode title, and IMDb score.
At four or more, the card returns to the large-number format to protect email length.
The user with the most qualifying watch time wins; total plays break an exact-time tie. Every scheduled weekly email shows the same anonymous movie-play, TV-play, and total-time aggregate. Only the winner’s card receives the gold border, larger trophy, and “YOU WON” treatment.
Trending ranks a media title and displays its server-wide play count once. Binge Champion ranks a person by qualifying watch time, but never reveals that person’s name, user ID, or watched titles.
The compact 1–3 movie recap now matches the rest of the newsletter’s movie hierarchy.
Each row includes a mini poster, title, genre, and Rotten Tomatoes critic and audience scores.
Show the first two genres and append “, and more” when additional genres exist.
When metadata has no genre, the rating line moves up automatically with no blank space.
This includes production and portable previews, individual tests, six-state test suites, scheduled weekly newsletters, and all applicable recipient states.
Exactly one watched movie or one streamed episode no longer becomes a scalar under strict mode.
MovieItems and EpisodeItems are initialized and assigned explicitly as arrays before the adaptive renderer checks their counts.
Retains a valid zero-length array.
Remains an array instead of being unwrapped into a scalar.
Continues to render the adaptive poster rows normally.