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
Plugin: newsletter.php
Version: 2.1.0
Since: 2004
Last updated: 2025-12-26
Author: Treveur Bretaudière
License: Proprietary
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.
| 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 |
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) |
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 |
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.
| 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.
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.
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.
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)
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'
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.
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.
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.
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.
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.
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).
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
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.
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).
| 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 |
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 |
Beamreactor\Database\SQL — prepared statementsBeamreactor\Sanitizer\Parser — input validationframeheader() / framefooter() — frame systemsecure() — permission checksforbids() — access denied handlerrte-modern.js — WYSIWYG editormail() — PHP native mail function| 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 |