DownloadAdvertising Library and Campaigns Proposal
> Draft Notice: This proposal is a draft generated by AI and may not have been reviewed for accuracy. It is intended as a starting point for discussion and implementation planning.
Table of Contents
-
Overview
-
Current State
-
Implementation Progress
-
Goals
-
Non-Goals
-
Proposed Model
- Ad Library
- Dashboard Selection
- Auto-Posting Tie-In
- Future Sources
-
Data Model
-
Admin Experience
-
Dashboard Experience
-
Community Positioning
-
Terminal Server Advertising
-
Auto-Posting Workflow
-
Migration Plan
-
Next Steps
-
Open Questions
Overview
BinktermPHP now includes a structured ANSI advertising system. The legacy
flat-file bbs_ads/ workflow has been replaced in practice by a
database-backed ad library, dashboard rotation, and schedule-based echomail
campaigns.
This document now serves two purposes:
-
describe the implemented architecture
-
record the remaining gaps and future phases
The implemented system provides:
-
a sysop-managed ANSI ad library
-
a curated dashboard advertising pool
-
future expansion room for other sources such as echomail
-
campaign-oriented auto-posting controls
Current State
Current implementation:
-
ads now live in the database rather than the legacy flat `bbs_ads/` store
-
migration `v1.11.0.38_advertising_library.php` imports existing `bbs_ads/*.ans`
as enabled ads
-
`src/Advertising.php` manages the ad library, dashboard selection, campaign
execution, post history, duplicate hash warnings, and SAUCE stripping for
outbound posts
-
`templates/admin/ads.twig` provides library upload, edit, delete, ANSI
preview, and browser-based ANSI editing helpers
-
`templates/dashboard.twig` shows a multi-ad dashboard viewer with per-session
anti-repeat selection, manual previous/next controls, and keyboard navigation
-
`scripts/post_ad.php` posts from the database-backed library
-
`templates/admin/ad_campaigns.twig` provides campaign CRUD, schedule editing,
target management, assigned-ad management, and post history
-
`scripts/run_ad_campaigns.php` supports manual campaign execution
-
`scripts/binkp_scheduler.php` and `src/Binkp/Connection/Scheduler.php` process
due ad campaigns automatically
Remaining limitations:
-
no terminal server advertising yet
-
no tag-based campaign filtering in the campaign UI
-
no saved-from-echomail import flow yet
-
no dedicated eligibility preview explaining why a given ad will or will not
be selected by a campaign
-
no richer history detail for matched schedule slot or posted body snapshot
Implementation Progress
Completed work:
-
Database-backed ad library implemented
-
Legacy `bbs_ads/` import migration implemented and imported ads enabled by default
-
Admin ad library page implemented
-
ANSI preview modal and editor preview implemented
-
Browser-side ANSI editing helpers implemented
-
Duplicate-hash warning support implemented
-
Dashboard carousel implemented with per-session rotation
-
Dashboard arrow-button and keyboard navigation implemented
-
Duplicate ANSI payload de-duplication in dashboard selection implemented
-
Manual posting via `scripts/post_ad.php` updated to use the library
-
SAUCE stripping for preview and posting implemented
-
Campaign tables and campaign admin UI implemented
-
Multiple targets with per-target subject templates implemented
-
Day/time/timezone schedule rows implemented
-
Timezone selector implemented
-
Scheduler integration implemented through `binkp_scheduler`
-
Campaign post history table implemented
-
Admin navigation updated with a top-level `Ads` menu
-
Documentation added in `docs/Advertising.md`
Goals
-
Give the sysop a real library of ANSI ads they can upload and manage.
-
Allow the sysop to choose exactly which ads are eligible for the dashboard ad
window.
-
Support multiple dashboard rotation strategies later without redesigning the
storage model.
-
Add a clean tie-in for auto-posting ads to echomail areas.
-
Preserve compatibility with the existing `bbs_ads/` workflow during
transition.
-
Leave room for future sources such as saved ANSI art from echomail messages.
Non-Goals
-
Full ad sales, billing, or customer management
-
Analytics-heavy impression tracking in the first version
-
Rich WYSIWYG ANSI editing in-browser
-
Multi-format ad handling beyond ANSI in the first version
Proposed Model
Ad Library
Introduce a first-class ad library where each ad has:
-
stored ANSI content
-
a canonical title / slug
-
optional description / notes
-
source metadata
-
activation flags
-
posting metadata
The library should support at least these sources:
-
`upload`
-
`generator`
-
`echoarea_saved` for future use
Each ad should exist as a record in the database, with the ANSI payload stored
either in the database or in a managed storage path referenced by the record.
Recommended approach:
-
store metadata in the database
-
store ANSI payload directly in the database as text
-
keep a content hash in the database for deduplication warnings and provenance
This makes the ad library easier to back up, preview, import from echomail, and
manage without a second storage layer.
Dashboard Selection
The dashboard no longer pulls from ?all ads in storage.? Instead, it pulls from
a sysop-managed dashboard pool.
Each ad in the library can independently be:
-
stored but hidden
-
eligible for dashboard rotation
-
eligible for echomail auto-posting
-
both dashboard-eligible and auto-post-eligible
Implemented dashboard selection controls per ad:
-
`enabled`
-
`show_on_dashboard`
-
`dashboard_weight`
-
`dashboard_priority`
-
`start_at`
-
`end_at`
-
tags
These date-window fields are shared eligibility controls for presentation
surfaces such as the dashboard and terminal slots, not terminal-only metadata.
Future rotation modes the data model could support:
-
random
-
weighted random
-
round-robin
-
pinned-first then rotate remainder
The current implementation ships weighted random selection and does not expose a
configurable dashboard rotation mode.
Dashboard rotation should be user-session-aware rather than purely global.
That means the system should remember which dashboard ads have been shown during
the current user session and avoid immediately repeating the same ad for that
same user.
Current implementation:
-
weighted random selection from eligible dashboard ads
-
per-session memory to avoid showing the same ad twice in a row to the same
user
-
session-backed memory so the same ad is not immediately repeated when multiple
eligible ads exist
This gives a better user experience for active users refreshing or revisiting
the dashboard without requiring a globally synchronized rotation state.
Auto-Posting Tie-In
Auto-posting should be modeled as campaigns or schedules, not a single global
boolean.
Recommended concept:
-
an Ad Campaign targets one or more area+domain destinations
-
a campaign chooses ads from an eligible pool
-
a campaign defines posting schedule and subject policy
Implemented campaign fields:
-
campaign name
-
enabled flag
-
from-user
-
to-name
-
ad selection mode
-
eligible ads
-
one or more area+domain targets, each with its own subject template
-
one or more schedule entries, each with days of week, post time, and timezone
Notes on current behavior:
-
campaign selection currently operates as weighted random
-
the campaign UI exposes `weighted_random` only
-
optional tag filtering is not implemented yet
Recommended schedule model:
-
campaigns should use explicit schedule rows rather than a raw minute interval
-
a schedule row defines:
- enabled flag
- one or more days of week
- time of day
- timezone
-
a campaign may have multiple schedule rows
Example:
-
Monday, Wednesday, Friday at `10:00`
-
Saturday at `20:00`
This lets the same library feed both the local dashboard and outbound ad posts
without coupling those two concerns too tightly.
Future Sources
The library should anticipate future import flows:
-
save ANSI art from echomail into the ad library
-
import generated ads directly from the ad generator UI
-
clone an existing ad into a new edited version
For future provenance, the ad record may later include optional source fields such
as:
-
source type
-
source message id
-
source echoarea
-
source filename
-
imported by user id
This avoids redesign when ?save from echomail? is added later.
Data Model
Suggested new tables:
advertisements
Core ad records.
Implemented fields:
-
`id`
-
`slug`
-
`title`
-
`description`
-
`content`
-
`content_hash`
-
`source_type`
-
`legacy_filename` nullable
-
`created_by_user_id` nullable
-
`updated_by_user_id` nullable
-
`is_active`
-
`show_on_dashboard`
-
`allow_auto_post`
-
`dashboard_weight`
-
`dashboard_priority`
-
`start_at` nullable
-
`end_at` nullable
-
`created_at`
-
`updated_at`
Deferred provenance fields for future echomail import work:
-
`source_message_id` nullable
-
`source_echoarea` nullable
advertisement_campaigns
Auto-post definitions.
Implemented fields:
-
`id`
-
`name`
-
`description`
-
`is_active`
-
`from_user_id`
-
`to_name`
-
`selection_mode`
-
`last_posted_at` nullable
-
`last_posted_ad_id` nullable
-
`created_at`
-
`updated_at`
Legacy compatibility fields still present in the current schema/service layer
but no longer used by the scheduling model:
-
`post_interval_minutes`
-
`min_repeat_gap_minutes`
advertisement_campaign_schedules
Per-campaign schedule rows.
Implemented fields:
-
`id`
-
`campaign_id`
-
`days_mask`
-
`time_of_day`
-
`timezone`
-
`is_active`
days_mask can be a simple bitmask for Sunday through Saturday. A normalized
one-row-per-day model would also work, but a bitmask keeps the admin UI simpler
when a sysop wants one time to apply to multiple days.
advertisement_campaign_targets
Per-campaign posting targets.
Implemented fields:
-
`id`
-
`campaign_id`
-
`echoarea_tag`
-
`domain`
-
`subject_template`
-
`is_active`
advertisement_campaign_ads
Join table between campaigns and eligible ads.
Implemented fields:
-
`campaign_id`
-
`advertisement_id`
-
`weight`
advertisement_tags
Implemented fields:
Examples:
-
`general`
-
`door`
-
`network`
-
`event`
Tags should be free-form rather than pre-seeded or hardcoded. The example tags
above are suggestions, not required built-ins.
advertisement_tag_map
Implemented fields:
-
`advertisement_id`
-
`tag_id`
advertisement_post_log
Audit and dedupe history for manual and automatic posting.
This is the shared post history table for:
-
manual "post now" actions from the admin UI
-
legacy CLI-triggered posts
-
scheduled campaign posts
Implemented fields:
-
`id`
-
`advertisement_id`
-
`campaign_id` nullable
-
`message_id` nullable
-
`echoarea_tag`
-
`domain`
-
`subject`
-
`posted_by_user_id` nullable
-
`post_mode`
-
`posted_at`
-
`status`
-
`error_text` nullable
Optional later table:
advertisement_impressions
Only if dashboard analytics become important later. This should not be required
for the first implementation.
Admin Experience
The current Admin -> Advertisements page is now the ad library manager.
Current sections and capabilities:
Library List
Columns:
-
title
-
source
-
active/inactive
-
dashboard enabled
-
auto-post enabled
-
updated at
-
actions
Actions:
-
preview
-
edit metadata
-
enable/disable
-
include/exclude from dashboard
-
include/exclude from auto-posting
-
delete
-
duplicate
-
post now
Upload
Upload ANSI files into the library rather than directly into bbs_ads/.
Metadata on upload:
-
title
-
optional slug override
-
description
-
tags
-
mark for dashboard
-
mark for auto-posting
If the uploaded ANSI hash already matches an existing ad, the UI should warn but
still allow the upload.
Dashboard Pool Management
This can either live inside the ad library or on a dedicated subpage.
Implemented controls:
-
which ads are shown on the dashboard
-
ad weight / priority
-
upload/edit preview from the ad library UI
Not yet implemented:
-
preview of the current eligible dashboard pool as a dedicated management view
-
rotation mode setting
-
automatic carousel timing or display mode controls
Campaigns
Implemented campaign page capabilities:
-
list campaigns
-
create/edit campaign
-
assign ads to campaign
-
manage multiple area+domain targets with separate subject templates
-
manage one or more schedule rows with day-of-week and time selection
-
test post / post now
-
enable/disable schedule
-
inspect post history
Not yet implemented:
-
tag-based campaign filtering
-
campaign eligibility preview / explanation
Dashboard Experience
The dashboard ad window currently keeps the basic card presentation:
-
ANSI preview rendered in the existing dashboard ad card
Its selection source now comes from eligible dashboard ads in the library.
Current selection order is effectively:
-
active ads only
-
`show_on_dashboard = true`
-
within active time window
-
de-duplicate duplicate ANSI payloads by content hash
-
choose using weighted random with session-aware anti-repeat behavior
Implemented presentation:
-
single card
-
manual carousel
The carousel should be treated as a dashboard presentation mode over the same
eligible ad pool, not as a separate ad type.
Current controls:
-
manual next/previous controls
-
keyboard advancement for accessibility and keyboard-centric use
Automatic advance is not implemented today.
Optional later improvements:
- rotate after page refresh threshold
- support dashboard categories or themed sets
Community Positioning
This proposal should be treated as a community-edition feature.
That means the full advertising workflow described here should be available to
all installations, including:
-
the ANSI ad library
-
dashboard pool management
-
carousel display modes
-
schedule-based auto-posting
-
multi-target campaigns
-
posting history
-
future echomail-based ad import
This keeps the advertising system aligned with the project principle that core
BBS/community-building features should remain broadly available.
Terminal Server Advertising
Terminal server advertising should reuse the same ad library as the web
dashboard and campaigns, but it should have its own presentation and eligibility
controls. Terminal display is more constrained than the web UI, so this should
be modeled as a separate delivery surface rather than reusing
show_on_dashboard.
Recommended Placement
First implementation should support two explicit terminal slots:
-
`login` - show once after successful login and before the main menu
-
`main_menu` - show in a dedicated framed area or panel on the main menu
These are the highest-value placements because they are visible, predictable,
and do not interrupt message reading, file browsing, or command entry.
Not recommended for the first version:
-
inline ads inside message readers
-
inline ads inside file listings
-
prompt-time or command-entry ads
-
random interruption during navigation
Possible later slot:
-
`transition` - show when returning to the main menu from major sections
Selection Model
Terminal ads should be selected from a terminal-specific eligible pool.
Recommended selection rules for a terminal slot:
-
ad is active
-
ad is enabled for terminal display in the requested slot
-
ad is within its shared active date window (`start_at` / `end_at`)
-
duplicate ANSI payloads are de-duplicated by content hash
-
choose by weighted random
-
avoid immediate repeat within the same terminal session when multiple ads are eligible
Recommended session behavior:
-
`login` slot chooses one ad once per login session
-
`main_menu` slot keeps a stable ad for the session unless the user explicitly cycles
-
later enhancement: rotate after N returns to the main menu rather than every refresh
Selection state should be per terminal session, not global.
Recommended Data Model
Do not overload show_on_dashboard for terminal use. The cleanest model is a
slot mapping table so terminal placements can grow without repeatedly changing
the advertisements table.
Recommended table:
advertisement_terminal_slots
Suggested fields:
-
`advertisement_id`
-
`slot` - e.g. `login`, `main_menu`
-
`weight`
-
`priority`
-
`is_active`
This is preferred over a simple show_on_terminal boolean because it supports:
-
per-slot eligibility
-
per-slot weight/priority
-
future slots without schema churn
-
cleaner divergence between web and terminal presentation
Admin Experience
The ad editor should gain terminal-specific controls.
Recommended controls:
-
enable ad for terminal display
-
assign one or more terminal slots
-
set per-slot weight
-
set per-slot priority
-
preview ad in terminal mode
If the main menu slot has stricter size limits than the login slot, the admin UI
should validate those constraints before save or clearly warn when an ad is too
large for a slot.
Rendering Constraints
Terminal ads should follow stricter rules than dashboard ads:
-
ANSI-only rendering
-
SAUCE stripped before display
-
fixed-width terminal-safe layout
-
slot-specific height limits
-
no browser-only presentation assumptions
Recommended slot constraints:
-
`login` may allow a larger full-screen or near-full-screen ad
-
`main_menu` should use a smaller framed region with a strict height cap
If an ad is too tall for a slot, prefer validation or exclusion over runtime
cropping.
Shell Integration
The terminal shell should ask the advertising service for a terminal ad by slot.
The advertising system should decide eligibility and selection; the shell should
own placement and framing.
This keeps responsibilities clear:
-
`Advertising` owns content selection
-
terminal shell code owns layout and screen composition
Recommended First Implementation
Phase 1 terminal support should include:
-
`login` and `main_menu` slots only
-
slot-based eligibility using a mapping table
-
weighted random selection with session anti-repeat
-
terminal preview in the admin UI
-
no inline ads inside readers, prompts, or file listings
This gives terminal advertising real visibility without making the terminal UX
feel noisy or intrusive.
Auto-Posting Workflow
Auto-posting should reuse the library rather than reading arbitrary filenames
from disk.
Proposed flow:
-
Scheduler daemon or cron job asks for active ad campaigns.
-
For each campaign, determine whether one or more schedule rows are due.
-
Choose an eligible ad using the campaign selection rules.
-
Post it via the existing echomail posting pipeline.
-
Log the result in `advertisement_post_log`.
-
Update campaign `last_posted_at` and last-posted ad metadata.
This now complements scripts/post_ad.php rather than replacing it.
Recommended CLI evolution:
-
keep `post_ad.php` for compatibility
-
teach it to accept either `--ad=slug-or-id` or legacy filename
-
add a new scheduler-oriented script such as `scripts/run_ad_campaigns.php`
-
ensure manual CLI posts also write to `advertisement_post_log`
Schedule runner behavior:
-
the runner should evaluate each campaign schedule row in its configured
timezone
-
the runner should allow a small grace window so a periodic cron job does not
need to fire at the exact minute
-
the runner should record enough information to avoid duplicate posting for
the same campaign schedule slot if it runs multiple times in the same window
Campaign selection modes worth supporting later:
-
random
-
weighted random
-
least recently posted
-
strict round-robin
The current implementation uses weighted random only.
Migration Plan
Phase 1: Library Foundation
Status: completed
-
Advertisement library tables created.
-
Migration/import script added for `bbs_ads/*.ans`.
-
One ad record is created per existing legacy file.
-
Imported ads from `bbs_ads/` are marked active/enabled by default.
Phase 2: Admin Library UI
Status: completed
-
Upload/list/delete page replaced with a library manager.
-
Preview and metadata editing added.
-
`show_on_dashboard` and `allow_auto_post` toggles added.
-
Tags and duplicate-hash warning UI added.
Phase 3: Dashboard Pool
Status: completed
-
Dashboard ad selection now uses the library.
-
Per-session anti-repeat behavior added.
-
Single-card display plus manual next/previous browsing implemented.
-
Existing dashboard rendering preserved and enhanced.
Phase 4: Post-Now and Auto-Post Integration
Status: implemented with follow-up items remaining
-
`post_ad.php` updated to read from the library.
-
Campaign tables and admin pages added.
-
Multi-target campaigns with per-area subject templates added.
-
Explicit day/time/timezone schedule rows added and used by the scheduler.
-
Scheduler script for campaign posting added.
-
Day-of-week and time editing in the campaign UI added.
-
Post history and error tracking added.
-
Due campaign targets are processed in a deterministic order.
-
Legacy interval fields remain in parts of the schema/service layer but are no
longer used by scheduling behavior.
-
Remaining items: tag-based ad selection/filtering, richer history detail, and
a campaign eligibility preview are not yet implemented.
Phase 5: Future Content Sources
Status: not started
-
Add "save ANSI from echomail to ad library"
-
Add provenance UI and duplicate detection workflow
Next Steps
Recommended next work:
-
Add advertising support to the Terminal Server
-
Add tag-based campaign filtering / eligibility controls
-
Improve campaign history with matched schedule-slot detail
-
Add clearer campaign validation warnings for no active schedules, targets, or ads
-
Add "save ANSI from echomail" import into the ad library
-
Add a campaign eligibility preview to explain which ads can currently be selected
Future Phase: Terminal Server Advertising
-
moved to the dedicated Terminal Server Advertising section above
Open Questions
-
Should manual "post now" support posting to a whole multi-target campaign or
also allow targeting just one destination from that campaign?
-
Should dashboard carousel mode remember per-user position between refreshes,
or restart from the first selected ad on each dashboard load?
-
Should campaign logs keep only success/failure metadata, or also store a copy
of the rendered subject/body that was posted?
-
Should schedule rows use the board timezone by default, or always require an
explicit timezone on each row?
|