27 KiB
Ticket-Centric Spam Cleanup Handoff — 2026-07-09
Purpose
This handoff captures the current state of the new ticket-centric Redmine maintenance substrate and the spam cleanup workflow for the customer-service project.
The immediate business goal is to reduce visible spam clutter in customer-service without disrupting existing NMI production scripts or doing unsafe bulk Redmine writes.
Important Safety / Operating Rules
- Do not modify NMI scripts right now. Existing NMI cron scripts are reference material only.
- Dry-run first. Redmine mutations require explicit
--apply. - Reads do not impersonate. Ordinary sync/search/dry-run planning should use normal API access.
- Writes impersonate
rebot. Spam cleanup / ticket maintenance writes should useX-Redmine-Switch-User: rebot(Harry Botter), because Redmine activity logs should show automated cleanup under that account, not Jason. - Treat impersonation like sudo. Use it only where the write is supposed to happen under that agent account.
- LAN first. The validated target so far is the LAN Redmine copy at
http://192.168.50.170, projectcustomer-service. - Confirm status IDs before applying. Existing evidence suggests
status_id=5is closed, but verify before first real apply if uncertain.
Relevant Files
- Tool:
/home/jason/projects/redmine/redmine_tickets.py - Tests:
/home/jason/projects/redmine/tests/test_redmine_tickets.py - Plan:
/home/jason/projects/redmine/docs/ticket_sync_tool_plan.md - General project handoff:
/home/jason/projects/redmine/HANDOFF.md - Helpdesk API include docs:
/home/jason/projects/redmine/docs/redmine_issue_api_helpdesk_include.md - Existing contact tool:
/home/jason/projects/redmine/redmine_contacts.py
Why redmine_tickets.py Exists
The earlier redmine_contacts.py contact conveyor is useful for contact enrichment and contact-level routing, but it is not the best first tool for clearing visible spam from customer-service because the UI pain is ticket-level: the top/open tickets themselves are clutter.
redmine_tickets.py is the new ticket-centric substrate. It syncs enriched issue payloads, searches cached tickets, and runs idempotent ticket actions.
Modern Helpdesk Access
Do not recreate RebotSlick PullHelpdeskInfo behavior. The old separate helpdesk pull/cache pattern is obsolete because the local Redmine Helpdesk plugin now supports:
GET /issues/:id.json?include=journals,helpdesk
This returns normal Redmine issue data plus embedded helpdesk_ticket metadata, including contact/email/source fields.
Current Architecture
Ticket payload cache
Per-ticket JSON files are stored under a configurable cache directory, e.g.:
.cache/redmine_tickets_lan/customer-service/41788.json
.cache/redmine_tickets_lan/customer-service/41789.json
Ticket/action state
State is currently manifest-backed:
.cache/redmine_tickets_lan/manifest.json
But the code now separates payload cache from state storage:
TicketCachehandles ticket JSON cache + delegates state operations.ManifestTicketStateStoreis the current state backend.- The abstraction is intentionally small so SQLite can replace the manifest later for thousands of tickets.
Required state-store methods:
get_ticket_state(issue_id)
upsert_ticket_state(issue_id, fields)
has_action(issue_id, action_name)
record_action(issue_id, action_name, result, metadata=None)
Implemented Commands
Sync enriched tickets
cd /home/jason/projects/redmine
./redmine_tickets.py \
--base-url http://192.168.50.170 \
--project customer-service \
--cache-dir .cache/redmine_tickets_lan \
sync \
--status-id open \
--max-tickets 10 \
--order newest-updated-first
Behavior:
- fetches issue stubs/pages from Redmine
- fetches full issue payloads with
include=journals,helpdesk - writes only missing/updated tickets to cache
- second identical run skips unchanged tickets
Verified output:
Sync complete: seen=10 fetched=10 skipped_cached=0 written=10
Sync complete: seen=10 fetched=0 skipped_cached=10 written=0
Search cached tickets
./redmine_tickets.py \
--cache-dir .cache/redmine_tickets_lan \
--project customer-service \
search \
--max-tickets 10 \
--order newest-updated-first
Validated that this matches the UI top-open-ticket pattern and includes spam-looking tickets such as:
#41788 Up to 20% Off Select Fans & Blowers
#41789 Up to 20% Off Select Fans & Blowers
Spam check cached tickets
./redmine_tickets.py \
--cache-dir .cache/redmine_tickets_lan \
--project customer-service \
spam-check
Validated result:
#41788 SPAM score=2 from=reply@globalindustrial.com Up to 20% Off Select Fans & Blowers
#41789 SPAM score=2 from=reply@globalindustrial.com Up to 20% Off Select Fans & Blowers
Record local idempotency only (no Redmine mutation):
./redmine_tickets.py \
--cache-dir .cache/redmine_tickets_lan \
--project customer-service \
spam-check \
--apply
After this, a second spam-check checks 0 tickets because spam-check action markers are recorded.
Plan closure for spam tickets
Dry-run:
./redmine_tickets.py \
--base-url http://192.168.50.170 \
--project customer-service \
--cache-dir .cache/redmine_tickets_lan \
close-if-spam \
--close-status-id 5 \
--contact-ticket-limit 10
Validated output:
#41788 close_payload={"notes": "Closed as likely spam by redmine_tickets.py close-if-spam.", "status_id": 5} contact_id=621 contact_spam=4/10 likely_spammer=True Up to 20% Off Select Fans & Blowers
#41789 close_payload={"notes": "Closed as likely spam by redmine_tickets.py close-if-spam.", "status_id": 5} contact_id=621 contact_spam=4/10 likely_spammer=True Up to 20% Off Select Fans & Blowers
Close-if-spam complete: planned=2 apply=False
Dry run only. Re-run with --apply to close Redmine issues and record idempotency markers.
No Redmine mutation has been applied yet.
Actual Closure Command (Pending Approval)
After confirming status_id=5 is the correct closed status on the LAN copy, this is the command that would close the two planned tickets and record idempotency markers:
cd /home/jason/projects/redmine
./redmine_tickets.py \
--base-url http://192.168.50.170 \
--project customer-service \
--cache-dir .cache/redmine_tickets_lan \
close-if-spam \
--close-status-id 5 \
--contact-ticket-limit 10 \
--impersonate-user rebot \
--apply
Expected write behavior:
- sends
PUT /issues/:id.json - payload:
{ "issue": { "status_id": 5, "notes": "Closed as likely spam by redmine_tickets.py close-if-spam." } } - includes header:
X-Redmine-Switch-User: rebot - records local actions:
close-if-spam = closedcontact-spam-review = likely-spammerorsingle-ticket-spam
Tests / Verification
Current test command:
python3 -m unittest discover -s tests -p 'test_redmine_tickets.py' -v
Last verified:
Ran 11 tests
OK
Compile command:
python3 -m py_compile redmine_tickets.py tests/test_redmine_tickets.py redmine_contacts.py
Last verified: OK.
25-Ticket LAN Evaluation — 2026-07-09
Jason confirmed status_id=5 is Closed. A fresh throwaway cache was used so old action markers would not hide results:
.cache/redmine_tickets_lan_25_eval
Commands run:
./redmine_tickets.py \
--base-url http://192.168.50.170 \
--project customer-service \
--cache-dir .cache/redmine_tickets_lan_25_eval \
sync \
--status-id open \
--max-tickets 25 \
--order newest-updated-first
./redmine_tickets.py \
--cache-dir .cache/redmine_tickets_lan_25_eval \
--project customer-service \
spam-check \
--apply
./redmine_tickets.py \
--base-url http://192.168.50.170 \
--project customer-service \
--cache-dir .cache/redmine_tickets_lan_25_eval \
close-if-spam \
--close-status-id 5 \
--contact-ticket-limit 10
Verified output summary:
Sync complete: seen=25 fetched=25 skipped_cached=0 written=25
Spam check complete: checked=25 apply=True
Close-if-spam complete: planned=5 apply=False
SUMMARY cached_tickets=25
SUMMARY spam=5 not_spam=20
Closure candidates produced by dry-run:
#41753 Automated unfulfillable FBA inventory removal notification — from fba-noreply@amazon.com — contact_id=51 — contact_spam=10/10 — likely_spammer=True
#41760 Celebrate America's 250th: Save Up To 40% Off — from reply@globalindustrial.com — contact_id=621 — contact_spam=4/10 — likely_spammer=True
#41761 Celebrate America's 250th: Save Up To 40% Off — from reply@globalindustrial.com — contact_id=621 — contact_spam=4/10 — likely_spammer=True
#41767 — — from enews-noreply@qnap.com — contact_id=3162 — contact_spam=10/10 — likely_spammer=True
#41776 Your favorite sale ENDS THURSDAY ⏳ — from noreply@mail.vistaprint.com — contact_id=3795 — contact_spam=10/10 — likely_spammer=True
Important finding from first run: the scorer conflated spam/promotional clutter with automated operational notifications. Example: #41753 from fba-noreply@amazon.com is likely an automated Amazon/FBA operational notification, not normal spam.
No Redmine close mutation was applied during this 25-ticket evaluation.
Refined Promotional-Junk Evaluation — 2026-07-09
The scorer was refined to target Jason's immediate goal: quickly and reliably close obvious unsolicited promotional/sales junk, such as Walmart/Uline/GlobalIndustrial/Vistaprint/QNAP-style promotional mail and lead-gen/sales outreach from people trying to sell LDR something without an established relationship.
False-positive protections added:
automated-operational: Amazon/FBA/order/API/payment-style notifications are excluded from the default auto-close class.- Customer/vendor operational threads are protected from staff-reply contamination: LDR staff-authored journal notes are no longer scored as spam evidence.
- Standalone
SEOin customer operational context is not treated as a sales trigger.
Regression tests now cover:
- promotional sale email =>
spam-promotional - cleaning-service sales pitch =>
spam-promotional - Amazon/FBA notification =>
automated-operational, not spam - real FBA returns inquiry with LDR pricing reply => not spam
- customer operational context mentioning SEO => not spam
Fresh final cache:
.cache/redmine_tickets_lan_25_promotional_eval_final_1783600119
Final verified summary:
Sync complete: seen=25 fetched=25 skipped_cached=0 written=25
Close-if-spam complete: planned=6 apply=False
SUMMARY categories={'unknown-review': 17, 'automated-operational': 2, 'spam-promotional': 6}
Current safe close candidates from the final dry-run:
#41767 — from enews-noreply@qnap.com — QNAP newsletter/promo
#41776 Your favorite sale ENDS THURSDAY ⏳ — from noreply@mail.vistaprint.com
#41788 Up to 20% Off Select Fans & Blowers — from reply@globalindustrial.com
#41789 Up to 20% Off Select Fans & Blowers — from reply@globalindustrial.com
#41804 Do you serve e-commerce brands? — from d.elliott@fulfillmentviewpro.com — 3PL lead-gen/sales pitch
#41834 Japan’s “JC-STAR Level 1” — from enews-noreply@qnap.com — QNAP promotional/newsletter item
No Redmine close mutation was applied during this final evaluation.
LAN apply verification — 2026-07-09
Jason approved moving toward closing the obvious junk. The following command was run against the final 25-ticket cache:
./redmine_tickets.py \
--base-url http://192.168.50.170 \
--project customer-service \
--cache-dir .cache/redmine_tickets_lan_25_promotional_eval_final_1783600119 \
close-if-spam \
--close-status-id 5 \
--contact-ticket-limit 10 \
--impersonate-user rebot \
--apply
Applied closures:
#41767
#41776
#41788
#41789
#41804
#41834
Idempotency check:
Close-if-spam complete: planned=0 apply=True
API verification after apply:
41767 status=Closed close_note_author=Harry B. close_note_found=True
41776 status=Closed close_note_author=Harry B. close_note_found=True
41788 status=Closed close_note_author=Harry B. close_note_found=True
41789 status=Closed close_note_author=Harry B. close_note_found=True
41804 status=Closed close_note_author=Harry B. close_note_found=True
41834 status=Closed close_note_author=Harry B. close_note_found=True
Current Limitations / Known Next Improvements
manifest.jsonis acceptable for the current tracer, but SQLite should be considered once caches/actions cover thousands of tickets.- Spam scoring is still intentionally simple and reviewable. It now distinguishes
spam-promotional,automated-operational, andunknown-review; future classes can be added as patterns emerge. - Contact-level spammer handling is only advisory right now. The tool reports
likely_spammer=True, but does not yet tag the contact. - First real close apply for each newly expanded batch should happen on LAN only, then immediately re-sync/verify status and activity author.
- The production Redmine path should remain out of scope until LAN write behavior is proven and reviewed.
50-Ticket Refined Promotional-Junk Dry Run — 2026-07-09
Fresh cache:
.cache/redmine_tickets_lan_50_promotional_eval_refined_1783600628
Verified summary:
Sync complete: seen=50 fetched=50 skipped_cached=0 written=50
Close-if-spam complete: planned=9 apply=False
SUMMARY categories={'unknown-review': 32, 'spam-promotional': 9, 'automated-operational': 9}
Promotional close candidates:
#41685 Samsung promo — noreply_samsung@sea.samsung.com — "Jason, we’ve missed you. We just can't stop innovating."
#41687 QNAP promo — enews-noreply@qnap.com — "Qsync Now Supports File Backup on Mac"
#41698 Vistaprint promo — noreply@mail.vistaprint.com — "Life’s best moments, treasured forever"
#41705 Popmenu/restaurant promo — noreply@popmenu.com — "Take a break from cooking!"
#41722 3PL lead-gen/sales pitch — hello@offer.search3pl.com — "Live fulfilment leads waiting for you to connect"
#41738 Vistaprint promo — noreply@mail.vistaprint.com — "Up to 15% off top business products that’ll draw them in"
#41746 Business Journals events/nominations promo — replycincinnati@engaged.bizjournals.com — "Hi LDR, here are our upcoming events and nominations"
#41760 Global Industrial sale — reply@globalindustrial.com — "Celebrate America's 250th: Save Up To 40% Off"
#41761 Global Industrial sale — reply@globalindustrial.com — "Celebrate America's 250th: Save Up To 40% Off"
Operational/system notifications excluded from auto-close:
#41694 USPS eReceipt
#41704 2024 UNPAID INVOICES $3,413.55
#41714 Undelivered Mail Returned to Sender
#41716 Critical security alert
#41726 UPS Invoice Now Available
#41753 Automated unfulfillable FBA inventory removal notification
#41765 Amazon Listings API changes
#41772 Cintas Payment Status
#41823 Amazon order confirmation
No Redmine mutation was applied during this 50-ticket dry run.
50-ticket LAN apply verification — 2026-07-09
Jason approved applying the 9-candidate promotional set. The following command was run against the exact reviewed cache:
./redmine_tickets.py \
--base-url http://192.168.50.170 \
--project customer-service \
--cache-dir .cache/redmine_tickets_lan_50_promotional_eval_refined_1783600628 \
close-if-spam \
--close-status-id 5 \
--contact-ticket-limit 10 \
--impersonate-user rebot \
--apply
Applied closures:
#41685
#41687
#41698
#41705
#41722
#41738
#41746
#41760
#41761
Idempotency check:
Close-if-spam complete: planned=0 apply=True
API verification after apply:
41685 status=Closed close_note_author=Harry B. close_note_found=True
41687 status=Closed close_note_author=Harry B. close_note_found=True
41698 status=Closed close_note_author=Harry B. close_note_found=True
41705 status=Closed close_note_author=Harry B. close_note_found=True
41722 status=Closed close_note_author=Harry B. close_note_found=True
41738 status=Closed close_note_author=Harry B. close_note_found=True
41746 status=Closed close_note_author=Harry B. close_note_found=True
41760 status=Closed close_note_author=Harry B. close_note_found=True
41761 status=Closed close_note_author=Harry B. close_note_found=True
Recommended Next Session Steps
- If Jason approves the current 27-candidate production 200-ticket set, apply closures on production using
--impersonate-user rebotand the exact cache.cache/redmine_tickets_prod_200_promotional_eval_1783612885. - Optionally apply the 3
maybe-customercontact tags from the same cache. - Re-sync/inspect via Redmine API/UI and verify closed status, close-note author
Harry B., second-apply idempotency, and local action markers. - Continue with another bounded production dry-run batch.
Customer identification update — 2026-07-09
Jason confirmed the following contacts/patterns are customers and should be tagged Customer:
koki-takeda@shinkiya-inc.com
hello@bohoflow.co
hi5@bavariatrachten.com
maia@solride.org
mike@solride.org
info@u2wear.com
sales@u2wear.com
any Cyberstorm email
Esire-family emails
Important correction: do not treat incidental substrings such as desiree in Indeed/applicant email aliases as Esire-family customer matches.
Production contact tagging was applied with impersonate_user='rebot':
MATCHED_COUNT 31
UPDATED_COUNT 25
SKIPPED_ALREADY_CUSTOMER 6
Two accidental applicant matches were corrected immediately:
4253 conversation-desireethrasher-customerreturnsinspect-0n3@indeedemail.com: removed Customer, left applicant
3934 conversation-desireethrasher-customerreturnsinspect-9nv@indeedemail.com: removed Customer, left applicant
After customer tagging, re-running the production 200 close plan still produced 27 candidates and kept #41238 removed from the plan.
Customer-tag safety gate — 2026-07-09
New hard rule: before closing any ticket as spam, redmine_tickets.py fetches the Helpdesk contact and checks tag_list. If the contact has tag customer, the ticket is not closed as spam. Absence of the customer tag does not imply spam.
Jason also clarified the closure risk model:
- Closed tickets are not deleted.
- If the other party replies to a closed Helpdesk ticket, Redmine reopens it as
New Message. - Therefore, stale 30+ day tickets that match a verified
spam-promotionalprofile are reasonable fast-close candidates once contact safety gates pass. - However, if a ticket was previously closed and then reopened because the contact messaged again, do not auto-close it again. Escalate instead: bump priority and optionally set a due date 1–3 days out.
A separate action now exists to tag contacts needing manual/customer review:
./redmine_tickets.py \
--base-url https://rm.ldrprep.com \
--project customer-service \
--cache-dir <cache> \
tag-maybe-customer [--apply]
This adds maybe-customer to contacts with inbound/established-business inquiry evidence, using --impersonate-user rebot for writes.
Tests now cover 20 cases, including the customer-tag skip and maybe-customer tag action.
Production 200-Ticket Dry Run With Customer Gate — 2026-07-09
Production cache:
.cache/redmine_tickets_prod_200_promotional_eval_1783612885
Re-running the close plan after adding the contact customer safety gate produced:
Close-if-spam complete: planned=27 apply=False
The customer-tag gate removed #41238 from the close plan, which was the suspicious manager@sonisha.com / Account Queries candidate.
Current 27 close candidates:
#41098
#41114
#41121
#41122
#41127
#41128
#41153
#41164
#41167
#41168
#41201
#41222
#41226
#41227
#41242
#41248
#41269
#41293
#41297
#41315
#41331
#41340
#41341
#41347
#41368
#41373
#41850
Maybe-customer dry-run produced 3 contact tag candidates:
contact_id=4470 source_issue_id=41424 tag_list=["maybe-customer"]
contact_id=3919 source_issue_id=41448 tag_list=["maybe-customer"]
contact_id=4476 source_issue_id=41504 tag_list=["maybe-customer"]
No production mutation was applied after adding this guard.
Production 100-Ticket Promotional-Junk Dry Run — 2026-07-09
Production URL:
https://rm.ldrprep.com
Fresh cache:
.cache/redmine_tickets_prod_100_promotional_eval_1783609390
Verified summary:
Sync complete: seen=100 fetched=100 skipped_cached=0 written=100
Close-if-spam complete: planned=20 apply=False
SUMMARY categories={'unknown-review': 59, 'automated-operational': 21, 'spam-promotional': 20}
Promotional close candidates:
#41397 Business Journals events/nominations promo — replycincinnati@engaged.bizjournals.com
#41430 Vistaprint sale — noreply@mail.vistaprint.com
#41431 Business Summit promo — replycincinnati@partner.bizjournals.com
#41657 Global Industrial Kee Safety promo — reply@globalindustrial.com
#41658 Global Industrial Kee Safety promo — reply@globalindustrial.com
#41685 Samsung promo — noreply_samsung@sea.samsung.com
#41687 QNAP promo — enews-noreply@qnap.com
#41698 Vistaprint promo — noreply@mail.vistaprint.com
#41705 Popmenu restaurant promo — noreply@popmenu.com
#41722 3PL lead-gen sales pitch — hello@offer.search3pl.com
#41738 Vistaprint promo — noreply@mail.vistaprint.com
#41746 Business Journals events/nominations promo — replycincinnati@engaged.bizjournals.com
#41760 Global Industrial sale — reply@globalindustrial.com
#41761 Global Industrial sale — reply@globalindustrial.com
#41767 QNAP newsletter/promo — enews-noreply@qnap.com
#41776 Vistaprint sale — noreply@mail.vistaprint.com
#41788 Global Industrial sale — reply@globalindustrial.com
#41789 Global Industrial sale — reply@globalindustrial.com
#41804 3PL lead-gen sales pitch — d.elliott@fulfillmentviewpro.com
#41834 QNAP promotional/newsletter item — enews-noreply@qnap.com
No production mutation was applied during this dry run.
Production apply verification — 2026-07-09
Jason approved applying the 20-candidate promotional set. The following command was run against the exact reviewed production cache:
./redmine_tickets.py \
--base-url https://rm.ldrprep.com \
--project customer-service \
--cache-dir .cache/redmine_tickets_prod_100_promotional_eval_1783609390 \
close-if-spam \
--close-status-id 5 \
--contact-ticket-limit 10 \
--impersonate-user rebot \
--apply
Applied closures:
#41397
#41430
#41431
#41657
#41658
#41685
#41687
#41698
#41705
#41722
#41738
#41746
#41760
#41761
#41767
#41776
#41788
#41789
#41804
#41834
Idempotency check:
Close-if-spam complete: planned=0 apply=True
API verification after apply:
41397 status=Closed close_note_author=Harry B. close_note_found=True
41430 status=Closed close_note_author=Harry B. close_note_found=True
41431 status=Closed close_note_author=Harry B. close_note_found=True
41657 status=Closed close_note_author=Harry B. close_note_found=True
41658 status=Closed close_note_author=Harry B. close_note_found=True
41685 status=Closed close_note_author=Harry B. close_note_found=True
41687 status=Closed close_note_author=Harry B. close_note_found=True
41698 status=Closed close_note_author=Harry B. close_note_found=True
41705 status=Closed close_note_author=Harry B. close_note_found=True
41722 status=Closed close_note_author=Harry B. close_note_found=True
41738 status=Closed close_note_author=Harry B. close_note_found=True
41746 status=Closed close_note_author=Harry B. close_note_found=True
41760 status=Closed close_note_author=Harry B. close_note_found=True
41761 status=Closed close_note_author=Harry B. close_note_found=True
41767 status=Closed close_note_author=Harry B. close_note_found=True
41776 status=Closed close_note_author=Harry B. close_note_found=True
41788 status=Closed close_note_author=Harry B. close_note_found=True
41789 status=Closed close_note_author=Harry B. close_note_found=True
41804 status=Closed close_note_author=Harry B. close_note_found=True
41834 status=Closed close_note_author=Harry B. close_note_found=True
100-Ticket Refined Promotional-Junk Dry Run — 2026-07-09
Fresh cache:
.cache/redmine_tickets_lan_100_promotional_eval_refined_1783601115
Verified summary:
Sync complete: seen=100 fetched=100 skipped_cached=0 written=100
Close-if-spam complete: planned=9 apply=False
SUMMARY categories={'unknown-review': 68, 'spam-promotional': 9, 'automated-operational': 23}
Promotional close candidates:
#41227 Global Industrial clearance — reply@globalindustrial.com — "Clearance: Bestsellers At Best Prices"
#41297 Vistaprint sale — noreply@mail.vistaprint.com — "Up to 30% off select customer faves, like mugs, postcards & more"
#41340 Global Industrial AC/fans — reply@globalindustrial.com — "Advanced AC, Wall Fans, And Cooling Units"
#41373 TikTok Shop promo — taylor@shop.tiktok.com — "The simplest way to start selling this June"
#41397 Business Journals events/nominations promo — replycincinnati@engaged.bizjournals.com — "Hi LDR, here are our upcoming events and nominations"
#41430 Vistaprint sale — noreply@mail.vistaprint.com — "Email exclusive! Up to 40% off select top products ENDS TOMORROW"
#41431 Business Summit promo — replycincinnati@partner.bizjournals.com — "Regional Business Summit Returns August 5"
#41657 Global Industrial Kee Safety promo — reply@globalindustrial.com — "'Kee Safety' Modular & Durable Fall Prevention Equipment"
#41658 Global Industrial Kee Safety promo — reply@globalindustrial.com — "'Kee Safety' Modular & Durable Fall Prevention Equipment"
False-positive protections added/verified during this 100-ticket run:
- VoIP voicemail / call transcription tickets are
automated-operational, not promotional spam. - Inbound buying/relationship inquiries like RePackify Gaylord purchase interest and u2wear returns-provider follow-up remain
unknown-review, not auto-close. - Receipts, bounces, security alerts, invoices, Amazon/FBA/order notices remain
automated-operational, not auto-close.
No Redmine mutation was applied during this 100-ticket dry run.
100-ticket LAN apply verification — 2026-07-09
Jason approved applying the 9-candidate promotional set. The following command was run against the exact reviewed cache:
./redmine_tickets.py \
--base-url http://192.168.50.170 \
--project customer-service \
--cache-dir .cache/redmine_tickets_lan_100_promotional_eval_refined_1783601115 \
close-if-spam \
--close-status-id 5 \
--contact-ticket-limit 10 \
--impersonate-user rebot \
--apply
Applied closures:
#41227
#41297
#41340
#41373
#41397
#41430
#41431
#41657
#41658
Idempotency check:
Close-if-spam complete: planned=0 apply=True
API verification after apply:
41227 status=Closed close_note_author=Harry B. close_note_found=True
41297 status=Closed close_note_author=Harry B. close_note_found=True
41340 status=Closed close_note_author=Harry B. close_note_found=True
41373 status=Closed close_note_author=Harry B. close_note_found=True
41397 status=Closed close_note_author=Harry B. close_note_found=True
41430 status=Closed close_note_author=Harry B. close_note_found=True
41431 status=Closed close_note_author=Harry B. close_note_found=True
41657 status=Closed close_note_author=Harry B. close_note_found=True
41658 status=Closed close_note_author=Harry B. close_note_found=True