Draft — This document was generated by AI and may not have been reviewed for accuracy.
Echomail robots watch echo areas for matching messages and automatically process them. The framework is designed to be generic and extensible: any number of robot rules can be configured, each pointing at an echo area with a processor that knows how to handle that type of message.
A robot rule (echomail_robots table) links an echo area to a processor. When the runner executes, it:
- Queries the echo area for messages newer than its last-processed cursor (
last_processed_echomail_id) - Optionally filters by a case-insensitive subject substring (
subject_pattern) - Passes each matching message to the configured processor
- Advances the cursor and updates run statistics
Robots are stateless between runs — progress is tracked entirely in the database row.
* * * * * php /path/to/binkterm-php/scripts/echomail_robots.php --quiet
# Run all enabled robots
php scripts/echomail_robots.php
# Run a specific robot by ID
php scripts/echomail_robots.php --robot-id=3
# Preview without writing anything
php scripts/echomail_robots.php --dry-run
# Suppress output
php scripts/echomail_robots.php --quietOutput for each robot:
[OK] Robot #1 (FSX_DAT iBBS): examined=42 processed=38
[ERROR] Robot #2 (Bad Robot): examined=0 processed=0 error=Unknown processor type: fake_type
Navigate to Admin → Area Management → Echomail Robots. Click the ▶ Run Now button on any robot row to execute it immediately and see a summary toast.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | |
name |
VARCHAR(100) | Human-readable label |
echoarea_id |
INTEGER FK | Echo area to watch |
subject_pattern |
VARCHAR(255) | Substring match on subject (case-insensitive, ILIKE). Leave null to process all messages. |
processor_type |
VARCHAR(100) | Matches a registered processor's getProcessorType() |
processor_config |
JSONB | Optional processor-specific configuration |
enabled |
BOOLEAN | Inactive robots are skipped by the runner |
last_processed_echomail_id |
INTEGER | Cursor — next run starts at id > this |
last_run_at |
TIMESTAMPTZ | Timestamp of last execution |
messages_examined |
INTEGER | Running total of messages seen |
messages_processed |
INTEGER | Running total of messages handled |
last_error |
TEXT | Last exception message, if any |
| File | Purpose |
|---|---|
src/Robots/MessageProcessorInterface.php |
Interface all processors must implement |
src/Robots/EchomailRobotRunner.php |
Fetches messages, dispatches to processors, updates cursors |
src/Robots/Processors/IbbsLastCallProcessor.php |
Built-in processor for iBBS last-call announcements |
scripts/echomail_robots.php |
CLI entry point |
Create a class in src/Robots/Processors/ that implements MessageProcessorInterface:
<?php
namespace BinktermPHP\Robots\Processors;
use BinktermPHP\Robots\MessageProcessorInterface;
class MyProcessor implements MessageProcessorInterface
{
private \PDO $db;
public function __construct(\PDO $db)
{
$this->db = $db;
}
public static function getProcessorType(): string
{
return 'my_processor'; // stored in echomail_robots.processor_type
}
public static function getDisplayName(): string
{
return 'My Processor';
}
public static function getDescription(): string
{
return 'Does something useful with matching messages.';
}
public function processMessage(array $message, array $robotConfig): bool
{
// $message contains echomail table columns:
// id, subject, from_name, to_name, message_text, echoarea_id,
// date, origin, etc.
//
// $robotConfig is the decoded processor_config JSONB from the robot rule.
//
// Return true if the message was handled, false to skip/ignore.
$text = $message['message_text'] ?? '';
if (empty($text)) {
return false;
}
// ... do work ...
return true;
}
}Then register it in EchomailRobotRunner::PROCESSORS:
private const PROCESSORS = [
'ibbslastcall_rot47' => IbbsLastCallProcessor::class,
'my_processor' => \BinktermPHP\Robots\Processors\MyProcessor::class,
];The new processor will immediately appear in the admin UI's processor-type dropdown.
The array passed to processMessage() is a raw row from the echomail table. Commonly used columns:
| Column | Description |
|---|---|
id |
Message ID |
subject |
Message subject |
from_name |
Author name |
to_name |
Recipient name (All for broadcast messages) |
message_text |
Full message body |
echoarea_id |
Echo area ID |
date |
Message date string |
origin |
Origin line |
The processor_config JSONB column allows per-robot configuration for a processor. Pass anything you need — the decoded array is provided as $robotConfig to every processMessage() call. Currently the admin UI does not expose a config editor; values can be set directly in the database or added to the modal in a future update.
Handles BBS node announcement messages posted to the FSX_DAT echo area by iBBS software. These messages have the subject ibbslastcall-data and a ROT47-encoded body.
Recommended robot rule settings:
| Field | Value |
|---|---|
| Echo Area | FSX_DAT (or whichever area carries these messages) |
| Subject Pattern | ibbslastcall-data |
| Processor Type | ibbslastcall_rot47 |
Message body format (after ROT47 decode, newline-separated):
Line 0 >>> BEGIN marker
Line 1 Sysop name
Line 2 BBS name
Line 3 Date (MM/DD/YY or YY/MM/DD)
Line 4 Time
Line 5 Location
Line 6 Operating system
Line 7 telnet-host:port
Line 8 >>> END marker
On a successful parse the processor upserts a row in bbs_directory. If the BBS name already exists (case-insensitive), the sysop, location, OS, and telnet address are updated and last_seen is refreshed. Manual entries (source = 'manual') are updated in place but their source flag is preserved.
ROT47 maps every printable ASCII character in the range 33–126 by rotating 47 positions: chr((ord(c) - 33 + 47) % 94 + 33). It is its own inverse — applying it twice returns the original text.
The interface is designed to work for netmail as well. When needed:
- Create a
netmail_robotstable (same shape asechomail_robots, substitutingto_address VARCHARforechoarea_id) - Add
src/Robots/NetmailRobotRunner.php— identical logic, queries thenetmailtable - Add
scripts/netmail_robots.phpCLI entry point
All existing MessageProcessorInterface implementations will work without modification; they receive the same $message array shape regardless of source table.