Lecteur Markdown
NEWSLETTER_DOCUMENTATION
XDP Plugin: Newsletter #
Plugin: `newsletter.php`
Version: 2.1.0
Since: 2004
Last updated: 2025-12-26
Author: Treveur Bretaudière
License: Proprietary
---
Overview #
Batch newsletter sending system with text and HTML support, WYSIWYG editor, configurable batch sending, per-recipient tracking, and dual-audience subscriber management (registered users + visitor subscriptions). Operates in two modes: admin interface for creation/sending, and public subscribe/unsubscribe form for embedding in pages.
Files #
| File | Role |
|---|---|
| `newsletter.php` | Main plugin — admin interface + embedded public form |
| `newsletter_subscribe.php` | Standalone subscription form |
| `newsletter_send.mod.php` | Scheduler module for automated batch sending (JSON API) |
| `newsletter.conf.inc.php` | Configuration (batch size, access level) |
| `newsletter_en_inc.php` | English locale |
| `newsletter_fr_inc.php` | French locale |
| `newsletter.sql` | Table creation script |
Database #
Table: `newsletter` #
| Column | Type | Description |
|---|---|---|
| `id` | INT (PK) | Auto-increment |
| `user_id` | INT | Creator user ID |
| `subject` | VARCHAR(998) | Email subject (RFC 2822: 1000 octets max, -2 for CRLF) |
| `body_text` | TEXT | Plain text content (or HTML fallback) |
| `newsletter_html` | TEXT | HTML content |
| `format` | ENUM | `text` or `html` |
| `status` | ENUM | `draft`, `queued`, `sending`, `sent`, `failed` |
| `total_recipients` | INT | Total recipient count |
| `sent_count` | INT | Successfully sent counter |
| `created_at` | DATETIME | Creation timestamp |
| `sent_at` | DATETIME | Completion timestamp (NULL if in progress) |
Table: `newsletter_recipients` #
| Column | Type | Description |
|---|---|---|
| `id` | INT (PK) | Auto-increment |
| `newsletter_id` | INT | FK → `newsletter.id` (CASCADE delete) |
| `user_id` | INT | User ID (0 for visitor subscribers) |
| `email` | VARCHAR(255) | Recipient email |
| `status` | ENUM | `pending`, `sent`, `failed` |
| `sent_at` | DATETIME | Sent timestamp |
| `error_message` | TEXT | Error details on failure |
Table: `newsletter_subscription` #
| Column | Type | Description |
|---|---|---|
| `id` | INT (PK) | Auto-increment |
| `email` | VARCHAR(255) | Visitor email (UNIQUE) |
| `subscribed_at` | DATETIME | Subscription timestamp |
Stores non-registered visitor subscriptions. Registered users use the `email_bot` flag in the main user table instead.
Security Model #
| Level | Constant | Capabilities |
|---|---|---|
| Moderator | `NEWSLETTER_LEVEL_MODERATOR` | Create, preview, queue, send newsletters |
| Scheduler module | `NEWSLETTER_LEVEL_MODERATOR` | Trigger batch sending via cron |
Public form (subscribe/unsubscribe) requires no authentication.
Sanitization #
Subject: `Parser::sanitize(..., 'string', [], 998)` — max 998 chars per RFC 2822.
Body text: `Parser::sanitize(..., 'string', [['WORDWRAP' => 75]], 65535)` — standard email line width + MySQL TEXT limit.
HTML body: `Parser::sanitize(..., 'html', ['moderator'], 65535)` — tag filtering (script, iframe, etc.).
Subject encoding: `=?UTF-8?B?...?=` per RFC 2047 for non-ASCII characters.
Architecture #
Dual-mode Operation #
Admin interface (`$obj == 'newsletter.php'`): creation, preview, queue, batch send, history. Requires `NEWSLETTER_LEVEL_MODERATOR`.
Public form (`$obj != 'newsletter.php'` or via `newsletter_subscribe.php`): toggle subscribe/unsubscribe widget, embeddable in any page. No authentication required.
Subscriber Sources #
Newsletter recipients are aggregated from two sources at queue time:
1. Registered users: `SELECT FROM {$cfg['dbtable']} WHERE email_bot='1' AND activated='1'`
2. Visitor subscriptions: `SELECT FROM newsletter_subscription` — with deduplication against registered users (visitors already in the user table are removed from `newsletter_subscription` and not double-counted)
Data Flow #
Registered users → {$cfg['dbtable']}.email_bot ('0'/'1')
Visitor subscribers → newsletter_subscription.email
↓
queueNewsletter()
[deduplication + merge]
↓
newsletter_recipients (status='pending')
↓
sendBatch() ×N
↓
newsletter.status='sent'
Actions #
list (default) #
URL: `?obj=newsletter.php`
Displays table of all newsletters with subject (clickable), creator username, status badge, recipient count, sent count with percentage, and creation date. "Create newsletter" button links to the create form.
create #
URL: `?obj=newsletter.php&action=create`
Form fields: subject (max 998 chars, required), format toggle (text/html radio), text textarea (20×80) or RTE HTML editor (800×400, full toolbar with source toggle). Displays active subscriber count. Blocks creation if zero subscribers. Submits to `action=preview`.
preview #
URL: `?obj=newsletter.php&action=preview` (POST)
Validates subject and body. For HTML format, generates a plain text fallback via `strip_tags()` with 75-column wordwrap. Displays the preview with buttons to queue, edit, or cancel.
queue #
URL: `?obj=newsletter.php&action=queue` (POST)
Inserts the newsletter record, retrieves all subscribers (registered + visitors), deduplicates visitor entries against the user table, and inserts all recipients as `status='pending'`. Displays confirmation with recipient count and link to start sending.
send_batch #
URL: `?obj=newsletter.php&action=send_batch&id={id}`
Fetches next batch of pending recipients (default 50, configurable via `NEWSLETTER_BATCH_SIZE`). Constructs MIME headers, sends via `mail()`, tracks per-recipient success/failure. Displays sent/failed/remaining counts. If remaining > 0, offers "Send next batch" button. When complete, sets newsletter status to `sent`.
view #
URL: `?obj=newsletter.php&action=view&id={id}`
Read-only view of a newsletter: subject, creator, status, recipient/sent counts, dates, and content preview (HTML rendered + text fallback, or plain text).
Email Format #
Plain text: `Content-Type: text/plain; charset=UTF-8; format="flowed"` — single-part.
HTML: `Content-Type: multipart/alternative` with MIME boundary `BeamReactor_{website}_{uniqid}`. Contains text/plain fallback part followed by text/html part. Ensures clients without HTML rendering receive a readable version.
From: `$cfg[11]['noreply']` (priority) or `$cfg[10]` (fallback).
X-Mailer: `BeamReactor/2.0`
Scheduler Module #
`newsletter_send.mod.php` provides a JSON API endpoint for automated batch sending, intended for cron job integration:
*/5 * * * * curl -s "https://example.com/?mod=newsletter_send"
If no specific newsletter ID is provided, it picks the oldest `queued` or `sending` newsletter. Returns JSON with `success`, `sent`, `failed`, `remaining`, and `status` fields.
Subscribe / Unsubscribe #
Toggle mechanism: same form, same action. If the email is already subscribed, it unsubscribes. If not, it subscribes.
Registered users: toggles `email_bot` field (`'0'`/`'1'`) in the user table.
Visitors: INSERT into / DELETE from `newsletter_subscription`.
Available as embedded form (when `newsletter.php` is included with `$obj != 'newsletter.php'`) or as standalone plugin (`newsletter_subscribe.php`).
Configuration #
| Variable | Usage | Default |
|---|---|---|
| `NEWSLETTER_BATCH_SIZE` | Emails per batch | 50 |
| `$cfg[10]` | Default sender email | — |
| `$cfg[11]['noreply']` | Noreply email (priority) | — |
| `$cfg['dbtable']` | User accounts table | — |
| `$charset` | Email charset | `UTF-8` |
| `$website` | Site name (MIME boundary) | — |
| `$basedisplevel` | Config access level | `BASE_LEVEL_MODERATOR` |
Locale #
File: `getlocale('newsletter')` — Variable: `$dialnewsletter[]`
| Range | Content |
|---|---|
| 0–8 | Validation errors, sending feedback |
| 9–18 | List view labels, subscriber count |
| 19–27 | Create/edit form labels |
| 28–38 | Queue and batch sending messages |
| 39–41 | Detail view labels |
| 60–67 | Subscribe/unsubscribe form |
Dependencies #
- `Beamreactor\Database\SQL` — prepared statements
- `Beamreactor\Sanitizer\Parser` — input validation
- `frameheader()` / `framefooter()` — frame system
- `secure()` — permission checks
- `forbids()` — access denied handler
- `rte-modern.js` — WYSIWYG editor
- `mail()` — PHP native mail function
Configuration Reference #
| Config | Usage |
|---|---|
| `$cfg[0]` | Site base URL |
| `$cfg[10]` | Default sender email |
| `$cfg[11]['noreply']` | Noreply email address |
| `$cfg[17]` | Max file upload size |
| `$cfg['dbtable']` | User accounts table name |