Official Technical Guide Zero-Build PHP 8.2+ Architecture Shared Hosting Optimized

Documentation & System Encyclopedia

Comprehensive technical guide, UI element directory, and shared-hosting optimization manual for MarbleSend.

Filter:
Core

System Architecture & Hosting Foundation

Philosophy: Self-hosted, single-tenant, zero-build, shared hosting friendly

MarbleSend is built from the ground up to solve a notorious pain point in email marketing software: the need for expensive dedicated VPS instances, Redis servers, node build pipelines, or complex Docker orchestration. MarbleSend runs on standard shared hosting (cPanel, Hostinger, DirectAdmin, Plesk, LiteSpeed) using pure PHP 8.2+ with zero server-side compilation.

100% Prepared PDO

Zero SQL Injection

Every database query uses strict prepared statements without ORM overhead.

AES-256-GCM

At-Rest Encryption

SMTP credentials, AI API keys, and webhook secrets encrypted in Crypto.php.

Atomic Queue

No Double-Sends

Worker claims queue batches atomically via MySQL status updates with strict deadline handling.

Zero-Build UI

Instant Deployment

Pre-compiled Tailwind CSS & vendored Alpine.js. Upload zip, run installer, done.

0.1 Zero-Build Hosting & Runtime Philosophy

Most modern web applications demand Node.js build pipelines, Redis daemon servers, Docker orchestration, or root VPS access. MarbleSend rejects this complexity. Designed specifically for accessible shared hosting (cPanel, Hostinger, Plesk, LiteSpeed), it runs on pure PHP 8.2+ with zero server-side compilation.

Zero Server Compilation

No npm, Webpack, Vite, or Composer required on the production server. Tailwind CSS is pre-compiled into static assets, Alpine.js is vendored locally, and PHPMailer 6.9.x is committed directly in vendor-lite.

Low Memory Footprint

Peak memory usage remains under 32MB even when processing email queue batches. Runs smoothly within strict shared hosting memory limits (64MB-128MB php.ini allocations).

No Forbidden Functions

Strictly avoids dangerous functions commonly disabled by shared hosting providers (exec, shell_exec, system, passthru, eval, proc_close). All external operations use native cURL with timeouts.

0.2 Security Perimeter & Directory Isolation

MarbleSend implements a strict two-tier directory layout that separates public web assets from sensitive application code and storage, eliminating directory traversal and file exposure risks.

Isolated Document Root (public/)

Only the public/ directory is exposed to the web server. Point your domain or subdomain DocumentRoot to /public. Even if directory listing is enabled in Apache/Nginx, core source files cannot be browsed.

Protected Application Core (app/)

Controllers, services, libraries, views, and configuration live outside document root in app/. Database credentials in app/config/config.php and encryption keys are unreachable via direct HTTP requests.

Secure Storage & Migrations

The storage/ directory (containing worker logs) and migrations/ (containing raw SQL schema files) reside completely outside public/, preventing unauthorized downloads of system logs or database structures.

0.3 Zero-ORM Database & PDO Prepared Statements

Object-Relational Mappers (ORMs) like Eloquent or Doctrine introduce massive memory overhead, complex hydration layers, and unpredictable N+1 query patterns. MarbleSend utilizes direct native PHP PDO with 100% prepared statements.

IMMUNITY

100% Prepared Statements

Every single database query binds parameters explicitly (PDO::PARAM_STR, PDO::PARAM_INT). SQL query structures and user data never mix, providing mathematical immunity against SQL injection attacks.

PERFORMANCE

Predictable High-Performance Queries

Direct SQL execution eliminates ORM object hydration overhead, executing list operations and batch queue updates up to 5x faster with minimal CPU consumption on shared database servers.

IDEMPOTENCY

Idempotent Schema Migrations

Database versions are tracked via numbered SQL files (0001_initial.sql through 0019_add_feedback_attachment.sql) executed by an idempotent runner with IF NOT EXISTS guards and recorded in cm_migrations.

SINGLE-TENANT

Single-Tenant Data Isolation

Designed as single-tenant standalone software without tenant_id columns. Indexes remain compact, sequential scans are avoided, and there is zero risk of cross-tenant data leaks.

0.4 At-Rest Encryption & Cryptographic Standards

Sensitive secrets are never stored in plain text. MarbleSend encrypts credentials using industry-standard authenticated symmetric cryptography through App\Lib\Crypto.

AES-256-GCM

AES-256-GCM Encryption

Uses Advanced Encryption Standard in Galois/Counter Mode (AES-256-GCM), providing both confidentiality and cryptographic integrity verification to detect any tampering.

RANDOM_BYTES()

Unique 96-bit Random IV

Every encrypted value generates a cryptographically secure 12-byte (96-bit) random Initialization Vector (IV) via random_bytes(), ensuring identical plaintexts produce entirely distinct ciphertexts.

SECRETS CATALOG

Encrypted Secret Catalog

Automatically protects SMTP relay passwords, BYOK AI API keys (OpenAI, Claude, Gemini, DeepSeek, Kimi, OpenRouter), Webhook signing secrets, and Envato Purchase Code.

MASTER KEY ISOLATION

Master Key Isolation

The 32-byte ENCRYPTION_KEY is generated during installation and stored exclusively in app/config/config.php (chmod 0600). The database never contains the encryption key.

0.5 Dual-Worker Engine & Concurrency Safety

MarbleSend delegates all time-intensive and scheduled tasks to a unified background worker runner, ensuring the admin dashboard remains fast and responsive at all times.

UNIFIED RUNNER

Single Master Background Runner

worker.php coordinates queue batch sending, recurring schedule execution, RSS feed post polling, autoresponder sequence triggering, and unopened campaign resends.

CLI & WEB-CRON

CLI Cron & Web-Cron Flexibility

Run via native system crontab (* * * * * php /path/to/worker.php) or via secure external HTTP ping to /cron.php?key=YOUR_KEY. Both pathways execute identical underlying service logic.

ATOMIC CLAIM

Atomic Queue Claiming (No Double-Sends)

Worker instances claim queue batches using atomic SQL status updates (UPDATE cm_email_queue SET status = 'processing' WHERE status = 'pending' LIMIT N). Even concurrent runs never send duplicate emails.

HEARTBEAT RECOVERY

Heartbeat & Stale Job Recovery

Tracks execution heartbeat in cm_settings. If a worker process is interrupted mid-send (e.g. host timeout), subsequent worker runs automatically detect and recover stalled queue items.

0.6 Server Requirements & Hosting Compatibility Matrix

MarbleSend is verified compatible with all standard shared hosting environments, control panels, and server configurations meeting baseline PHP 8.2+ requirements.

Component Minimum Requirement Recommended Operational Notes
PHP Runtime PHP 8.2.0+ PHP 8.2 or 8.3 strict_types=1 declared across all application files.
Database Engine MySQL 8.0+ / MariaDB 10.6+ MariaDB 10.11+ or MySQL 8.0+ InnoDB engine required. UTF8MB4 full Unicode charset.
Required Extensions pdo_mysql, openssl, mbstring, json, curl, simplexml, sodium All standard core extensions enabled Standard on 99% of shared hosting. Optional: intl for locale formatting.
Web Server Apache, Nginx, LiteSpeed, Caddy Apache + mod_rewrite or LiteSpeed Pre-configured .htaccess files included in root and public/.
Memory Allocation 32MB memory_limit 64MB - 128MB Queue chunks processed in conservative batches to prevent out-of-memory.
Cron Scheduler CLI crontab OR Web-Cron HTTP ping CLI cron every 1 minute Web-Cron fallback endpoint (/cron.php?key=...) for hosts without CLI access.

Tested Hosting Control Panels

Fully tested and verified on cPanel, Hostinger hPanel, Plesk, DirectAdmin, CyberPanel, AApanel, CloudPanel, and vanilla Ubuntu/Debian LAMP/LEMP stacks.

Module 01

Dashboard Overview (/dashboard)

Aggregated mission control for audience health, delivery KPIs, worker heartbeat, and schedule automation.

Dashboard Component Anatomy & Support Inspector

Hover or click any element below for backend details
KPI Card 1

Total Sent

Logs aggregation & Monthly counter
KPI Card 2

Opened / Open Rate

1x1 tracking pixel & Apple MPP note
KPI Card 3

Clicked / CTR

Click-through redirect injector
KPI 04

Unsubscribed Audience

Opt-out rate & RFC compliance
Operation Card 4

Worker Heartbeat

Cron status & 1-min interval
Visualizer

Volume Growth Chart

7d, 30d, 90d, YTD scale
Activity Feed

Recent Broadcasts Table

Status lifecycle & audit trail
Safety Banner

Deliverability Health

SMTP profile readiness

Database Impact:

Support Team Pro-Tip:

1.1 Email Performance KPI Cards

Element Calculation & Source Visual Meaning Support Prevention Rationale
Total Sent Card COUNT(cm_email_logs) + SUM(cm_campaigns.sent_count) Displays total lifetime broadcast and schedule emails dispatched. Subtext displays volume sent this calendar month. Prevents confusion when older log entries are pruned; historical campaign counters ensure lifetime totals never drop.
Total Opened / Open Rate COUNT(DISTINCT contact_id WHERE opened_at IS NOT NULL) / Total Sent * 100 Measures engagement via transparent 1x1 tracking pixel injected into HTML email footer. Apple MPP Disclaimer: iOS Mail Privacy Protection pre-fetches images via Apple proxy, which can artificially raise opens. CTR is the true business metric.
Total Clicked / CTR COUNT(DISTINCT contact_id WHERE clicked_at IS NOT NULL) / Total Sent * 100 Measures unique subscriber link clicks. Rewritten through /track/click.php. Unsubscribe links and confirmation links are excluded from campaign CTR to preserve statistical integrity.
Unsubscribed Card COUNT(status = 'unsubscribed') / Total Contacts * 100 Rate of recipient list opting out via the mandatory one-click unsubscribe mechanism. Healthy threshold is < 0.5%. Unsubscribe rate > 1% indicates audience fatigue, purchased lists, or low relevance.

Total Sent & Monthly Throughput

Tracks total lifetime emails successfully dispatched across broadcasts, recurring schedules, RSS feeds, and autoresponders. Subtext highlights volume dispatched in the current calendar month. Because MarbleSend aggregates historical campaign counters alongside active email delivery logs, lifetime metrics remain permanent and accurate even when older log rows are pruned for database maintenance.

Unique Opened & Apple MPP Dynamics

Recorded via a transparent 1x1 tracking pixel injected into HTML email footers. Opens are tracked uniquely per contact per campaign (first-touch timestamping). Crucial deliverability note: Apple Mail Privacy Protection (MPP) caches images via proxy servers, which can artificially inflate open counts. While Open Rate remains a valuable engagement trend indicator, Click-Through Rate (CTR) represents verifiable subscriber conversion intent.

Unique Clicked & URL Rewrite Engine

Every standard hyperlink in your email body is rewritten to route through /track/click.php, logging the contact ID, campaign ID, destination URL, and timestamp before redirecting the subscriber. To safeguard subscriber trust and legal compliance, unsubscribe links (/unsubscribe.php) and subscription confirmation links (/confirm.php) are strictly exempt from tracking redirects.

Unsubscribed Contacts & Domain Protection

Measures subscribers who opted out using either the mandatory email body unsubscribe link or the RFC 8058 one-click List-Unsubscribe header. A healthy list maintains an unsubscribe rate below 0.5%. When rates exceed 1.0%, MarbleSend warns operators to review list quality and sending cadence to prevent mailbox providers (Gmail, Yahoo, Outlook) from downgrading domain reputation.

1.2 Operations & Worker Health Heartbeat

MarbleSend operates on a single master background runner: worker.php. The dashboard inspects cm_settings['worker_last_run'] to determine if cron is firing every 60 seconds.

Active State (Cron Healthy)

When worker.php runs within the last 3 minutes, the badge displays Active. Queue batches are processed in real time, scheduled newsletters trigger on time, and autoresponders fire promptly.

Inactive State (Cron Not Detected)

If no heartbeat is recorded for > 3 minutes, the badge turns red. Clicking the card immediately navigates to Settings → System with the exact crontab CLI command and web-cron fallback URL.

Active Contacts vs Total Audience

Displays contacts in active status against the total subscriber base. MarbleSend enforces a strict 4-state lifecycle machine: active, pending (awaiting double opt-in confirmation), unsubscribed, and suppressed (bounced/complaint). Outbound campaign workers strictly filter WHERE status = 'active', ensuring unconfirmed or suppressed emails are never dispatched.

Monthly & Lifetime Campaign Volume

Summarizes broadcasts dispatched in the active calendar month alongside total lifetime campaigns. Provides immediate visibility into monthly email marketing cadence and operational output.

Next Upcoming Schedule Resolution

Queries active recurring schedules (ordered by next_run_at ASC LIMIT 1) and displays the next queued newsletter and countdown. All execution times are computed strictly by SchedulerService::computeNextRun() with full timezone awareness and stored in UTC, eliminating daylight saving time (DST) drift.

Worker Heartbeat & Shared Hosting Cron

The system heartbeat verifies whether worker.php has executed within the last 180 seconds. In shared hosting environments (cPanel, Hostinger), configure crontab to run * * * * * php /path/to/worker.php >/dev/null 2>&1. If CLI cron is unavailable, MarbleSend provides a secure Web-Cron fallback endpoint (/cron.php?key=YOUR_KEY) compatible with free external pingers like cron-job.org.

1.3 Campaign Growth Visualizer

The growth visualizer renders volume distribution across four selectable timeframes: 7 Days, 30 Days, 90 Days, and Year-to-Date (YTD). Each bar represents an aggregated time bucket, displaying sent count and open count on hover.

Selectable Timeframes (7d, 30d, 90d, YTD)

Allows instant switching between short-term weekly cadence (7 Days), monthly cycles (30 Days), quarterly performance (90 Days), and annual delivery volume (Year-to-Date).

Zero-Dependency Lightweight Flex Bars

Rendered using pure CSS flexbox heights calculated relative to peak sending volume. Eliminates heavyweight JavaScript charting libraries, loading instantly on mobile devices and low-spec hosting.

Dual-Metric Tooltip Telemetry

Hovering over any bar reveals the exact time interval, total messages dispatched, and unique open volume, allowing rapid comparison of engagement over time.

Live Sending & Heartbeat Activity Feed

The right-column feed surfaces recent campaign dispatches in real time, featuring animated status badges (Completed, Sending pulse, Failed, Draft), delivery ratios, and a persistent system heartbeat status card.

Completed

status = 'completed'

Sending

status = 'sending'

Failed

status = 'failed'

Draft

status = 'draft'

Campaign History & Delivery Audit Table

The bottom audit table lists recent broadcast executions with full metadata: campaign name, lifecycle status, origin tag (manual, recurring schedule, RSS feed, or unopened resend), recipient dispatch ratio, and verified open rate.

Campaign Name & Origin

Shows campaign subject, creation date, and origin tag (manual, recurring, rss, or resend_unopened).

Recipient & Delivery Ratio

Instant count of delivered vs. failed messages, plus live open rate and click rate percentages.

Analytics Report Link

Direct shortcut to the Campaign Report page for recipient-level event logs and the Unopened Resend modal.

1.5 Deliverability Health Check Warning Banner

When your sending domain lacks verified SPF, DKIM, or DMARC records, MarbleSend highlights an attention banner directly above the KPI metrics. Clicking Verify Domain DNS opens the 4-step diagnostic wizard under /settings?tab=deliverability, preventing users from burning domain reputation on unauthenticated mailboxes.

Active SMTP Profile Verification

Displays the number of active SMTP relay profiles configured in Settings. MarbleSend supports multi-SMTP rotation, allowing round-robin load distribution across multiple relays.

Missing Relay Warning & Preflight Guard

If no active SMTP profile is found, the banner displays a prominent red warning before campaigns can be launched, preventing fatal queue failure.

1.6 Quick Action Shortcuts & Workflow Automation

Direct action triggers linking the overview dashboard to high-velocity workflows: automated recurring newsletter schedules, RSS-to-email digest pipelines, subscriber list acquisition, double opt-in verification, and suppression protection.

Automate Flow (Recurring & RSS)

Launches automated email dispatches on fixed cron schedules or whenever a connected WordPress/Ghost/Substack RSS feed publishes new content, entirely hands-free.

Audience & Subscriber Management

Enables rapid subscriber imports via CSV, segmented list grouping, double opt-in compliance enforcement, and automatic suppression of bounced or unsubscribed addresses.

Module 02

Campaign Manager & Reports (/campaigns)

High-speed broadcast composer, tag-targeted recipient selector, preflight safety engine, and in-depth delivery reports.

Interactive Campaigns UI Inspector

Click any component to inspect schema impact and operational tips
PROGRESS

Live Dispatch Monitor

Real-time stats & events
AUDIENCE

Recipient Selector

Tag filtering & select-all
COMPOSER

Triple-View Editor

Code, split & preview
SAFETY

Preflight Check Engine

6-point automated inspection
TESTING

Test Send Diagnostic

Zero-queue live sample
ATTRIBUTION

UTM & Link Rewriter

UTM params & 302 click track
PORTFOLIO

Broadcast History

Status & origin filters
ANALYTICS

Post-Send Deep Report

CTR, MPP & auto-resend

Database Impact:

Support Team Pro-Tip:

2.1 Live Campaign Monitor Banner

Whenever a broadcast is actively dispatching, a sticky monitor banner slides into view at the top of /campaigns. It polls /api/campaigns/:id/progress every 1.5 seconds without full page refreshes.

Summer Launch Newsletter

Sending

74% complete

7,420 / 10,000 emails

Queued

2,528

Processing

40

Success

7,420

Failed

12

Recent Events Real-time status
ana.wijaya@example.com Delivered
budi.santoso@example.org Delivered
chen.li@example.net 550 5.1.1 User unknown
dewi.lestari@example.com Delivered
eko.prasetyo@example.org Processing
View Full Report

1.5-Second Polling Engine

Queries /api/campaigns/:id/progress asynchronously every 1.5 seconds, displaying live throughput without browser page reloads.

Recent Delivery Events Stream

Live event logger streaming the last 5 recipient status changes with real-time delivery confirmations and SMTP error diagnostics.

TELEMETRY NOTE

If background worker.php is delayed or cron is inactive, an alert badge appears with one-click troubleshooting instructions.

2.2 Recipient Selector & Tag Filters (Left Column)

The left column allows laser-focused subscriber targeting. Admins can filter by custom contact tags, search by partial email/name, and select individual contacts or hit Select All Matching.

State Machine Filtering Guarantee:

Regardless of selection or search, the query engine strictly enforces status = 'active'. Unsubscribed, pending, or suppressed contacts are automatically excluded from the queue.

Multi-Tag Intersections

Select specific subscriber segments by tags. The recipient selector dynamically computes intersection counts in real time.

Debounced Live Search (300ms)

Type-ahead search filtering contacts by name or email with a 300ms debounce to prevent database spikes on high-volume lists.

Master Selection Controls

Select individual contacts or use the Select All toggle to target all contacts matching the active filter with a single click.

Groups

Contacts are organised into groups (a contact can be in several; every contact is in at least one, General by default). In Select Recipients choose “By group” to send to every active contact in the picked groups — the full group, not just the page you are viewing — or “Pick manually” to choose individual contacts (optionally filtered by one group).

Groups can also be chosen as the audience of a schedule, as the trigger of an autoresponder rule, as the destination of a signup page, and to narrow a resend to non-openers. Deleting a group never deletes its contacts; autoresponder rules that depend on it are disabled.

2.3 Campaign Composer & Split Editor Modes

Split Mode

Simultaneous code editing on the left and live responsive iframe preview on the right.

Code Only Mode

Full-width distraction-free HTML code editor with gutter line numbers and syntax tabs.

Live Preview Mode

Sandboxed rendering with Desktop (100%), Tablet (600px), and Mobile (375px) device viewport toggles.

Placeholder Variables & Substitution Engine

Personalize emails dynamically using {{name}}, {{email}}, {{unsubscribe_link}}, {{offer_url}}, and {{track_pixel}} tags.

Sandboxed Responsive Viewports

Test responsive mobile (375px), tablet (600px), and desktop (100%) rendering inside an isolated iframe sandbox before launch.

2.4 The 6-Point Automated Preflight Safety Check

To prevent catastrophic mistakes (e.g. sending emails with broken placeholders, missing unsubscribe links, or spam triggers), MarbleSend requires a preflight check before launching.

Safety Check Severity Rule & Detection Consequence if Failed
1. Unsubscribe Link CRITICAL Checks for presence of {{unsubscribe_link}} token. Sending is BLOCKED. Mandatory under CAN-SPAM, GDPR, and 2024 Yahoo/Google Sender Requirements.
2. Placeholders Validity CRITICAL Detects unknown placeholder tags (e.g. typos like {{first_nam}}). Sending blocked until unknown tags are corrected or removed.
3. Recipient Audience CRITICAL Validates that > 0 active recipients are selected. Cannot broadcast to empty selection.
4. SMTP Connectivity CRITICAL Verifies selected SMTP profile host and credentials. Prevents queue stalls caused by bad relay credentials.
5. Spam Trigger Words WARNING Scans subject and body for spam trigger phrases ("100% free", "act now", "limited time"). Warning banner shown; admin can review and proceed.
6. Subject Line Length INFO Recommends 30–60 characters for optimal mobile subject truncation. Informational notice for higher open rates.

Preflight Verification Gate

Evaluates 6 automated safety rules before queue commit. Any CRITICAL check failure disables the launch button.

2.5 Test Send Diagnostic & Confirmation Modal

Before committing thousands of emails to the queue, the Send Test Email button dispatches an instant single test message directly to the administrator or QA mailbox without touching the database recipient list. All placeholder tags ({{first_name}}, {{email}}, {{unsubscribe_link}}) are populated with mock preview values so you can inspect email rendering across Gmail, Apple Mail, and Outlook.

Mock Tag Interpolation

Populates subscriber variables with realistic preview values (e.g. Jane Doe, demo unsubscribe link) for authentic visual verification.

Pre-Launch Verification Modal

Two-step modal confirmation displays recipient count, selected sender profile, and active UTM tags before queue creation.

2.6 Automated UTM Tracking Injection

All outbound anchor tags (<a href="...">) are processed through App\Services\TrackingInjector. UTM parameters (utm_source, utm_medium, utm_campaign, utm_content) are appended automatically before click-redirection wrapping. Mailto links, tel links, and unsubscribes are preserved untouched.

PIPELINE 01

Step 1: UTM Query Injection

Appends utm_source, utm_medium, utm_campaign, and utm_content to every hyperlink without altering existing query parameters.

PIPELINE 02

Step 2: Tracking Pixel Insertion

Injects a transparent 1x1 GIF image before the closing </body> tag to record open events upon email client render.

PIPELINE 03

Step 3: 302 Click Rewrite Engine

Rewrites outbound URLs to /track/click.php?h=... for millisecond-latency redirects with first-touch link telemetry logging.

Strict Compliance Exemptions

Unsubscribe links, mailto/tel protocols, and double opt-in confirmation URLs are strictly exempt from UTM and click wrapping.

2.7 Campaign History & Portfolio (/campaigns/history)

The full history portfolio provides multi-attribute filtering across origin (manual, recurring, rss, resend_unopened), status (completed, sending, failed, draft), and date range. Admins can multi-select campaigns to download aggregated CSV delivery logs.

Origin Tracking Pipeline

Categorizes broadcasts by source: Manual composer, Recurring schedules, RSS blog triggers, or Unopened resend clones.

Multi-Status Filtering & CSV Export

Filter archive by completed, sending, failed, and draft states. Download detailed delivery logs with a single click.

One-Click Broadcast Duplication

Clone any historical broadcast back into the composer with its full HTML layout, subject line, and sender settings preserved.

2.8 Analytics Reports & "Resend to Unopened" Feature

Optionally pick groups to resend only to non-openers who belong to them. A resend never goes beyond the original campaign's recipients.

Under /campaigns/:id/report, MarbleSend presents a comprehensive post-campaign report:

KPI METRICS

4 Bento Performance Metrics

Tracks Sent volume (100%), Delivery failures, Unique opens with open rate, and Unique clicks with CTR.

TEMPORAL CLUSTERS

Hourly Engagement Curve

Displays hourly open clusters across the initial 72 hours to discover optimal subscriber send timeframes.

ENGAGEMENT RECOVERY

Targeted Resend to Non-Openers

Clones the campaign targeting subscribers where opened_at IS NULL, allowing new subject lines to recover 15–25% more opens without emailing active readers twice.

Module 03

Contacts Hub & Growth Tools (/contacts)

Subscriber lifecycle management, CSV importing, GDPR compliance, custom signup pages, and autoresponders.

Interactive Contacts UI Inspector

Click any component to inspect schema impact and operational tips
LIFECYCLE

4-State State Machine

active/pending/unsub/supp
FILTERING

Search & Filter Engine

Tags, status & debounce
TELEMETRY

Audit & Consent Drawer

IP, timestamps & bounces
INGESTION

RFC 4180 CSV Importer

50k rows & dedup guards
FUNNELS

Hosted Lead Capture Pages

Double opt-in & funnels
AUTOMATION

Tag Autoresponders

Event-driven drip rules
MANAGEMENT

Bulk Management Hub

Cross-page segmentation
COMPLIANCE

GDPR Privacy & Erasure

JSON export & hard delete

Database Impact:

Support Team Pro-Tip:

3.1 The 4-State Subscriber State Machine

MarbleSend maintains a strict, unified state machine for all contacts. There are no side-boolean flags. Every single sending pipeline filters WHERE status = 'active'.

active

Active Subscriber

Confirmed opt-in, valid email. Eligible to receive campaigns, schedules, and autoresponders.

pending

Double Opt-In Pending

Signed up via double opt-in form, awaiting click on confirmation email. Excluded from broadcasts until confirmed.

unsubscribed

Unsubscribed

Clicked unsubscribe link. Never sent to again. Cannot be manually reactivated without explicit re-opt-in.

suppressed

Hard Bounce / Complaint

Permanently suppressed due to invalid mailbox (5xx SMTP), spam complaint, or manual admin suppression.

Status Transition Trigger Destination State Campaign Dispatch Impact
active Subscriber clicks unsubscribe link or RFC 8058 header unsubscribed Instantly excluded from all future broadcast and schedule queues.
active 5xx SMTP permanent bounce or 3rd consecutive soft bounce suppressed Quarantined permanently to shield domain sending reputation.
pending Subscriber clicks double opt-in confirmation token link active Promoted to active; eligible to receive campaigns and autoresponders.
suppressed Administrative unsuppress override with confirmation active Reset soft bounce counter; restored to active delivery queues.

Architecture Guard: Zero Side-Boolean Flags

MarbleSend enforces a single ENUM column (cm_contacts.status). Other platforms frequently suffer from data corruption when boolean flags like is_active, is_unsubscribed, and is_bounced conflict. In MarbleSend, every dispatch query contains WHERE status = 'active' without ambiguity.

3.2 Real-time Filters, Search & Row Actions

The Contacts table features instant Alpine.js reactive filtering by tag pills, subscriber status (active, pending, unsubscribed, suppressed), and free-text debounce search across email and first/last name.

Every contact belongs to at least one group (General by default). Use the Groups filter to narrow the table and Manage Groups to create, rename or delete groups — deleting a group never deletes its contacts.

JSON Tag Storage & Normalization

Tags are stored as normalized JSON arrays in cm_contacts.tags. Tags are case-insensitive, trimmed, and indexed for rapid filtering and segmentation.

Debounced 300ms Instant Search

Search queries filter simultaneously across email address, first name, and last name with a 300ms debounce to minimize unnecessary database overhead.

Server-Side Pagination & Scale

Effortlessly browses lists exceeding 50,000 subscribers using efficient SQL LIMIT and OFFSET queries with low memory utilization.

Direct Edit

Update name, custom metadata, and tag chips inline without reloading.

Manual Suppress

Instantly suppress a contact to immediately disqualify them from all queues.

GDPR Hard Delete

Permanently purges contact profile, logs, and queue items pursuant to GDPR Article 17.

3.3 Detail Drawer, GDPR Audit Trail & JSON Export

Clicking any contact row slides open the audit drawer. It presents lifetime metrics (total emails received, open rate, click count), opt-in IP address, opt-in timestamp, signup form slug, and a one-click Export GDPR JSON button to comply with Article 15 (Right of Access) and Article 20 (Data Portability).

Opt-in Timestamp & Consent Source

Records the exact date, time, and acquisition channel (Manual, CSV Import, Signup Page slug, or API) for legal verification.

Verified IP Address Capture

Captures subscriber IP address at the moment of signup form submission, providing indispensable proof of consent under GDPR and CAN-SPAM.

Lifetime Engagement Telemetry

Monitors cumulative broadcast delivery volume alongside verified open timestamps to identify active versus dormant subscribers.

Soft Bounce Auto-Escalation

Tracks consecutive temporary delivery failures (X / 3). Upon encountering the 3rd consecutive soft bounce, status automatically transitions to suppressed.

3.4 CSV Importer & Column Mapping

The importer parses CSV files up to 50,000 rows. It auto-detects delimiters (comma, semicolon, tab) and provides visual dropdowns to map columns to: Email, First Name, Last Name, and Tags.

Choose which groups the imported contacts join (General if none). Existing contacts are added to those groups; they are never removed from groups they already belong to.

Memory-Safe Chunked Upload

Streams files up to 50,000 rows in transactional chunks, maintaining memory usage below 32MB even on restrictive shared hosting.

Auto-Delimiter & Intelligent Mapping

Detects commas, semicolons, and tabs automatically, with interactive column mapping for Email, First Name, Last Name, and Tags.

Batch Tagging & Post-Import Audit

Applies a global tag to all imported contacts in the batch and displays an audit report of inserted, updated, and skipped records.

Skip Duplicates

Preserves existing contact details and tags. New rows only are inserted.

Update Empty Fields

Fills in missing names and appends new tags without overwriting existing data.

Overwrite All

Updates names and tags completely to match the incoming CSV file.

3.5 Public Signup Pages (/s/:slug)

Located under Tab 2 of Contacts. Create standalone branded subscription pages with custom headline, description, input labels, and submit buttons. Features include:

Pick the groups that subscribers from this page join (General if none). Groups deleted later drop out automatically.

Total Hosted Views

Monitors aggregate landing page impressions across all active signup funnels.

Average Conversion Rate

Calculates submission-to-view percentage across all hosted lead capture forms.

Active Pages / Cap

Shows active landing pages relative to the generous 20-page operational capacity.

30-Day Submissions

Tracks new subscriber acquisitions captured during the active rolling 30-day window.

Multi-Channel Embed Integration

Embed forms anywhere using direct hosted URL (/subscribe?slug=...), clean raw HTML embed code, responsive iframe, or JS modal popup.

Double Opt-in Engine

Generates HMAC-signed confirmation tokens with automated confirmation email dispatch.

Automatic Tagging

Assigns designated subscriber tags upon signup to trigger tailored drip sequences.

Custom Redirection

Directs new subscribers to your thank-you page or lead magnet URL after opt-in.

3.6 Autoresponders & Drip Sequences

Located under Tab 3 of Contacts. Configure automated welcome emails that are queued as soon as a new subscriber becomes active — right after signup, or after confirming the double opt-in. A rule matches a tag or a group; there is no delay setting. Emails are delivered by the background worker and respect suppression rules automatically.

A rule can fire on a group instead of a tag. Group rules run when a new active contact is created in that group or confirms by email — not when an existing contact is added to the group later (the same as tag rules). Deleting the group disables the rule.

Tag-Based Event Listener

Fires when a new active subscriber carries the rule's tag or belongs to its group — right after a signup page, API call or manual add (single opt-in), or once the double opt-in is confirmed. It does not fire for CSV imports, nor when a tag or group is added to an existing contact later.

Dedicated SMTP Routing

Routes transactional welcome emails through high-reputation dedicated SMTP profiles separate from marketing bulk broadcasts.

Asynchronous Worker Execution

Delegates email rendering and delivery to worker.php, preventing web timeouts and keeping form submissions instant.

Loop Prevention & Dedup Guard

Prevents duplicate rule triggers, ensuring subscribers never receive duplicate welcome emails if tagged repeatedly.

3.7 Bulk Management & List Segmentation (/contacts/list)

Under /contacts/list, manage high-volume lists with batch operations: select across pages, apply or remove tags in bulk, suppress thousands of cold leads simultaneously, or export targeted segments into UTF-8 CSV files.

Add to Group and Remove from Group apply to every selected contact. Removing a contact from its last group puts it back in General.

Bulk Tag Append

Appends tags to all selected contacts in a single query without removing existing subscriber tags.

Bulk Tag Removal

Surgically strips designated tags from selected contacts across multiple lists.

Filtered CSV Export

Streams filtered subscriber subsets directly to RFC 4180 CSV files with full metadata.

Bulk Suppression

Quarantines cold or unengaged contacts immediately to preserve sender reputation scores.

Bulk Unsubscribe

Processes bulk opt-out requests from external sources or offline campaign channels.

GDPR Hard Purge

Permanently removes subscriber records and associated telemetry logs in compliance with GDPR Article 17.

3.8 GDPR Privacy, Audit Export & Deletion Standards

Built-in privacy controls adhering strictly to European Union General Data Protection Regulation (GDPR), California Consumer Privacy Act (CCPA), and CAN-SPAM requirements.

The GDPR export (JSON) also lists the groups the contact belongs to.

Article 15 & 20: Machine-Readable JSON Export

Generates a complete, structured JSON dossier containing subscriber profile details, consent IP, opt-in timestamps, and full delivery logs.

Article 17: Right to Erasure (Hard Delete)

Executing a permanent delete cascades across cm_contacts, cm_email_logs, and cm_email_queue, destroying all personally identifiable data.

Deliverability Unsuppress Guard

Requires explicit administrative confirmation to restore a suppressed address, preventing accidental mailings to confirmed invalid mailboxes.

Module 04

Templates Studio & AI Copywriter (/templates)

8 Email-safe table layouts, sandboxed iframe preview with miniature CSS reset, sales-hook AI copywriter, and Markdown news posts for newsletters.

Templates Studio UI Inspector

Click any component below to view database query, schema impact, and operational tips
SIDEBAR

Template Library

Instant search & slug keys
PRESETS

Starter Layouts

8 table-based email layouts
EDITOR

Raw Code Editor

Tab indent & beautifier
PREVIEW

Sandboxed Viewport

Desktop & mobile frame
TOKENS

Dynamic Tokens Palette

1-click placeholder injection
DIAGNOSTIC

Test Email Sender

Live SMTP verification modal
AI COPY

AI Copywriter Drawer

Generate & refine templates
AI AUDIT

Subject Reviewer

1–100 score & spam audit
POSTS

News Posts

Markdown articles for newsletters

Database Impact:

Support Team Pro-Tip:

4.1 Template Library, Custom Slugs & Metadata Management

Centralized repository for all marketing, transactional, and autoresponder email templates. Features instant client-side search filtering, immutable URL slug IDs, and live draft status indicators.

Immutable Template Slugs

Slug IDs (e.g. #monthly-newsletter) are defined upon creation and permanently locked when saved. This guarantees automated RSS campaigns and trigger sequences never break due to modified identifiers.

Sub-Second Debounced Search

Fast client-side Alpine filter matches across template titles, subject lines, and unique slug IDs without triggering unnecessary database round-trips.

Live Draft State Synchronization

A dedicated dashed indicator card visually distinguishes active in-memory drafts from saved production templates, preventing accidental overwrites.

Independent Campaign Snapshots

Editing or deleting a library template never alters historical broadcasts. Dispatched campaigns store independent static HTML and subject snapshots in cm_campaigns.

4.2 8 Tested Table-Based Responsive Starter Layouts

Modern desktop email engines (notably Microsoft Outlook using Word HTML) do not support CSS Grid or Flexbox. MarbleSend ships with 8 responsive, table-based layouts battle-tested across Gmail, Outlook, Apple Mail, and Yahoo:

1. Minimal Blank

Clean canvas with required unsubscribe footer.

2. Product Launch

Hero banner, value proposition, bold CTA.

3. Monthly Newsletter

Curated editorial, 2-column post highlights.

4. News Posts Newsletter

Header band plus the {{news_posts}} block for News posts schedules.

5. Welcome Series

Personal onboarding, 3-step getting started.

6. Webinar Invitation

Date/time badge, speaker bio, RSVP button.

7. Blog Digest

Numbered articles, reading time, author tags.

8. Win-Back Offer

Urgent discount coupon, loss-aversion copy.

Nested Table Architecture & Inlined CSS

Every starter layout is structured using nested <table> elements with explicit pixel constraints, zero cellpadding/cellspacing, and pre-inlined CSS to ensure layout stability across all email clients.

4.3 Raw HTML Code Editor & Clean Formatter

A focused code editor engineered for email developers, featuring tab indent trapping, real-time character count telemetry, and an automated whitespace normalizer.

Tab Key Indentation Trapping

Pressing Tab inserts 2-space indentation instead of shifting browser focus, allowing seamless code structuring directly within the browser.

One-Click Code Beautifier ("Clean")

Normalizes inconsistent indentation, removes redundant carriage returns, and cleans nested table markup without breaking inline style definitions.

Payload Weight & Truncation Guard

Real-time character counter monitors template size. Keeping HTML weight below 102 KB prevents Gmail from clipping your message with a "[Message clipped]" footer.

Zero Visual WYSIWYG Bloat

Direct HTML control prevents unwanted CSS classes, proprietary web fonts, and hidden span tags generated by visual drag-and-drop builders from corrupting email deliverability.

4.4 Sandboxed Multi-Device Responsive Viewports

Simulates desktop and mobile email clients within an isolated iframe sandbox, protecting the dashboard interface from rogue styles while providing instant visual feedback.

Iframe Security & CSS Isolation

The sandbox="allow-same-origin" attribute completely isolates email CSS rules from bleeding into the MarbleSend admin interface, preventing styling conflicts.

Desktop vs 320px Mobile Switcher

Toggle instantly between full desktop container width and a realistic 320px mobile viewport with device border styling to verify single-column responsiveness.

Real-Time Subject Simulation Bar

Top preview header displays the rendered subject line in real-time, showing how personalization placeholders will look to the recipient before launching.

Dynamic Syntax Warning Banner

Automatically scans draft markup and displays an alert banner if unclosed placeholder tags or unsupported merge tokens are detected.

4.5 Dynamic Personalization Tokens & RFC Compliance

At delivery time, the template engine dynamically replaces tokens with recipient attributes, secure tracking links, syndicated RSS article summaries, and News posts.

{{name}}

Recipient full name (falls back to "there" if empty)

{{email}}

Recipient email address

{{unsubscribe_link}}

Mandatory one-click RFC 8058 unsubscribe link

{{confirm_link}}

Double opt-in confirmation URL for signup pages

{{posts}}

Syndicated blog post loop for automated RSS newsletters

{{feed_title}}

Title of the syndicated RSS/Atom content feed

{{latest_post_title}}

Headline of the most recently published blog article

{{posts_count}}

Count of new feed posts included in the newsletter

{{news_posts}}

Block of News posts for a Newsletter schedule (cover, title, subtitle, body)

{{news_posts_count}}

Number of News posts included in this send

{{news_latest_post_title}}

Title of the newest News post in this send

Legal Requirement & Deliverability Safeguard

Every marketing broadcast must include {{unsubscribe_link}}. Broadcasts sent without a valid unsubscribe mechanism violate CAN-SPAM, GDPR, and CASL, leading to aggressive domain blacklisting by major ISPs.

4.6 Single Test Send Diagnostic & Pre-Delivery Check

A modal-driven diagnostic tool allowing senders to dispatch test emails to any recipient address via selected SMTP profiles before scheduling or broadcasting.

Multi-SMTP Profile Selection

Route test transmissions through any configured SMTP profile to verify SPF, DKIM, and deliverability status on specific mail routing servers.

Synthetic Placeholder Rendering

Automatically substitutes personalization tokens with realistic mock data (e.g. "John Doe") to inspect visual formatting and alignment accurately.

Synchronous Queue-Bypass Dispatch

Bypasses the background queue (cm_email_queue) to deliver test messages synchronously via MailerFactory, providing immediate pass/fail feedback.

Inbox & Dark Mode Quality Assurance

Send tests directly to personal Gmail, Outlook, and Apple Mail accounts to audit dark-mode color inversions, button contrast, and typography rendering.

4.7 Hooked AI Copywriting Engine BYOK

Integrated AI Copywriter operating directly via BYOK (Bring Your Own Key) to generate and refine high-converting, email-safe templates with zero third-party proxy latency.

Crucial Principle: Output Quality Governed by Prompt & Model

MarbleSend does not restrict you to a single closed AI provider. Because generation runs via your configured BYOK provider, the resulting email layout quality, persuasive strength, HTML table accuracy, and subject critique are fundamentally governed by two variables: the specificity of your prompt instructions and the reasoning power of the selected LLM model.

1. User Prompt Specificity & Context Depth

The AI model reflects the clarity and context of the instructions you provide. Generic prompts such as "write a promo email" produce generic copy. For superior results, provide structured context: target audience, pain points, core offer, desired tone of voice (urgent, conversational, executive), and a specific Call-to-Action (CTA). In the Edit Template tab, give targeted iterative instructions (e.g., "shorten the second paragraph", "emphasize the 20% discount on the CTA", or "add a 3-benefit callout box") to refine your template step-by-step.

2. LLM Model Tier & Reasoning Capability

Under Settings → AI Providers, you can connect frontier reasoning models (such as DeepSeek-V4.1-flash, Claude 5 Sonnet, or GPT-6) or lightweight local models (such as 7B–8B parameters via Ollama or Groq). Frontier models excel at intricate email-safe HTML table nesting, natural placement of personalization tokens ({{name}}, {{email}}, {{unsubscribe_link}}), and nuanced conversion copywriting. On the Subject Options tab, the live 1–100 score, spam risk analysis, and variant suggestions are generated directly by the active model — higher-tier models deliver dramatically sharper critique and more creative alternatives.

MarbleSend Built-in System Prompt Guardrails

Regardless of prompt length or model choice, AiService.php injects robust email-safe architectural guardrails: single-table responsive structure (max 600px width, clean canvas, high-contrast cards), table-based bulletproof CTA buttons compatible with Outlook and Gmail, zero external images to safeguard deliverability, and mandatory {{unsubscribe_link}} token insertion in footers to comply with global email regulations.

Full Template Generation

Produces complete, responsive table-based HTML email layouts tailored to your target audience, core offer, tone of voice, and specified reading length.

Context-Aware Refinement

Analyzes the active template in the editor and applies targeted iterative modifications (e.g. "add a 3-column feature section" or "make the CTA more urgent").

Interactive Visual Diff

Side-by-side sandboxed iframes render the "Before" and "After" versions simultaneously, allowing users to inspect changes visually before applying them.

How to Obtain API Keys Across Major Providers

Once you have obtained your API key and chosen model ID, configure it in MarbleSend with these simple steps:

6.7 Dual-Mode AI Engine: BYOK Providers & MarbleSend AI Credits

4.8 Interactive AI Subject Score & Deliverability Inspector

Scans email subject lines across your entire template library, evaluating open-rate probability, spam filter risk, and character truncation with 1-click variant suggestions.

Full Library Subject Loading

Scans all subjects across cm_templates and displays them in a scrollable list, highlighting the subject belonging to the currently edited template.

1–100 Multi-Factor Scoring

The active LLM analyzes curiosity, clarity, value proposition, and urgency, calculating an objective effectiveness score out of 100.

Mobile Truncation & Spam Filter Audit

Flags character counts exceeding mobile viewport limits (recommended <= 50 characters) and identifies spam-trigger words like "FREE", "URGENT $$$", or excessive punctuation.

High-Converting Variant Suggestions

Generates alternative subject lines focused on curiosity, benefit-driven, or loss-aversion angles, applyable directly to the editor with one click.

News Posts — Substack-style Articles for Newsletters

Write articles in the Posts tab of Templates (Markdown with live preview). A Newsletter schedule set to “News posts” sends each Ready post once, oldest first, by filling the {{news_posts}} placeholder of its template. Posts live only in your email — there is no public page.

Write and mark Ready

Use the toolbar or plain Markdown. A post is sent only after you switch it from Draft to Ready.

Template placeholder

Put {{news_posts}} where the articles should appear (the “News Posts Newsletter” starter already has it). {{news_posts_count}} and {{news_latest_post_title}} work in the subject.

Schedule it

In Schedules choose Newsletter, then Content source: News posts. Only posts made Ready after that moment are sent, so your archive is never replayed. Smart Suppression is not used.

Safe by design

Post text is HTML-escaped and only https links and images are allowed. Keep each email under about 100 KB so Gmail does not clip the unsubscribe link — the editor warns you.

Posts workspace at a glance

Open Templates and switch to the Posts tab. The library sits on the left and the editor with its live preview on the right — the same layout as the template studio.

PartWhat it does
LibraryLists every post with a Draft or Ready badge, how many times it was sent and when it last changed. The search box filters by title and subtitle; the trash icon appears on hover.
Title, subtitle, coverThe title is required (up to 255 characters, one line). The subtitle (up to 500) is shown under it. A cover image must be an https URL — anything else is rejected when you save.
Status switchDraft posts are never sent. Switching a post to Ready stamps the moment it became Ready; that moment decides which schedules may send it.
Markdown editorToolbar buttons (heading, bold, italic, link, lists, quote, divider, image) insert syntax around your selection. A post can hold up to 100,000 characters.
Live previewRenders the unsaved post inside the template you pick, using the same code as a real send, and shows the email size in KB. Placeholders such as {{name}} use sample values.
Placeholder shortcutA button under the editor copies {{news_posts}} so you can paste it into a template.

Markdown reference

Post text is written in a small, safe Markdown subset. Anything outside this list stays plain text.

SyntaxResult
## Heading
### Smaller
Heading (levels 1–3 become email-safe headings)
**bold** *italic*Bold and italic
[text](https://example.com)Link. Only http, https and mailto addresses are linked; any other scheme (such as javascript:) is left as plain text.
- item
1. item
Bulleted and numbered lists
> quoteBlock quote
---Divider line
![alt](https://example.com/a.png)Image. https only; always add a short description for readers who block images.
(blank line)Paragraphs are separated by a blank line; a single line break stays a line break inside the paragraph.
<script>alert(1)</script>Raw HTML is never interpreted: a <script> or <img> tag you type is shown as literal text.

The {{news_posts}} placeholder family

Place these in a template; a News posts schedule fills them at send time, before the usual per-recipient tokens such as {{name}} are resolved.

PlaceholderBecomes
{{news_posts}}The block of posts for this send: cover image, title, subtitle and body of each post, oldest first. Required in the template body.
{{news_posts_count}}The number of posts in this send, for example 1. Works in the subject and the body.
{{news_latest_post_title}}The title of the newest post in this send. Ideal for the subject line.

Required in the body

A schedule set to News posts is refused at save time if its template body has no {{news_posts}}, and a run fails safely (posts stay pending) if the template is edited later.

Never sent as raw text

Used anywhere else — a manual Campaign, an RSS schedule or a template-only schedule — the placeholders are replaced with empty text, and the Campaign pre-flight check warns you before sending.

Personalised and tracked

Post text goes through the normal pipeline: {{name}} inside a post is personalised per recipient, links get UTM parameters and click tracking, and the unsubscribe link in your template stays intact.

Post lifecycle

Every post moves through the same simple states; the library badge always shows the current one.

Draft

Still being written. Never sent. Moving a Ready post back to Draft clears its Ready time.

Ready

Queued for delivery. A schedule sends it only if it became Ready after that schedule's News posts mode was switched on. Saving an already Ready post does not change its place in the queue.

Sent

Tracked per schedule and shown as “Sent 1×” in the library. A schedule never sends the same post twice, even if you edit the post afterwards.

Deleted

Removes the post and its delivery history. Emails that were already delivered are not affected, and a deleted post cannot be recovered.

Email size and safety

Posts carry their full text inside the email — there is no public page — so size and safety matter more than in a link-only digest.

Escape first

Every character you type is HTML-escaped before any formatting is applied, so scripts and tags cannot reach a recipient's inbox.

https only

Images and the cover must be https. Links accept http, https and mailto. Other schemes are shown as plain text.

Stay under about 100 KB

Gmail clips messages over roughly 102 KB and hides the footer, including the unsubscribe link. The preview shows the size in KB and warns above 80 KB; keep posts per email low for long articles.

No public archive

Posts exist only inside the emails you send. There are no web pages, no image uploads and no automatic AI calls — images are linked by URL.

Quick troubleshooting

SymptomWhat to check
The preview shows no articlesThe selected template has no {{news_posts}} placeholder (it is marked “no {{news_posts}}” in the list). Pick another template or add the placeholder.
“Large email” warningShorten the post, remove heavy formatting or lower “Posts per email” in the schedule.
A link is not clickableWrite it as [text](https://…) with an http, https or mailto address; other schemes are left as text.
An image does not appearThe address must start with https and be publicly reachable. Some mail apps also block remote images until the reader allows them.
Module 05

Schedules & RSS Automation (/schedules)

Recurring newsletter dispatches (template or News posts), interactive monthly broadcast calendar, cadence engine, dynamic audience targeting, and automated blog RSS/Atom campaigns.

Schedules, RSS & News Posts UI Inspector

Click any component below to view database query, schema impact, and operational tips

Interactive Inspector
CALENDAR

Calendar & Month Matrix

Visual schedule map & cadence
CADENCE

Recurrence Engine

Daily, weekly, monthly & custom
RSS / ATOM

Automated Blog Poller

Hash deduplication & cache
AUDIENCE

Dynamic Audience

Tag query at exact run second
AUDIT

Occurrence Logs

Run audit & duplicate prevention
EXECUTION

On-Demand Run Now

Immediate run without cadence shift
OPTIMIZATION

Hosting Optimization

8 curated operational guidelines
WORKER

Cron Heartbeat & Lock

Non-blocking concurrency guard
POSTS

News Posts Mode

Substack-style newsletters

Database Impact:

Support Team Pro-Tip:

5.1 Dual-Mode Operations: Interactive Schedule Manager & Calendar View

MarbleSend provides two synchronized operating views tailored for different workflow needs: a compact Bento-grid Schedule Manager (/schedules) and an expansive interactive Monthly Calendar (/schedules/list).

Dense Tabular & Bento View

Displays all recurring broadcasts with next-run countdowns, active status toggles, selected template previews, and direct execution shortcuts.

Interactive 7-Column Calendar Grid

Maps upcoming deliveries against a standard Sunday–Saturday monthly calendar, featuring a current-day highlight, month switcher, and clickable event chips.

Selected Schedule Detail Drawer

Instant inspection drawer rendering sandboxed template previews, recurrence specs, recipient tag rules, and quick action controls.

5.2 Cadence Engine, Recurrence Rules & UTC Timezone Normalization

Recurring schedules are powered by SchedulerService::computeNextRun(), which eliminates manual timestamp calculation in favor of robust PHP DateTimeImmutable mathematics.

Universal UTC Storage with Localized Presentation

All next_run_at values are stored in UTC within cm_schedules. When rendered in the browser, times are converted to the admin's configured display timezone while preserving recipient delivery windows.

Daily & Weekly Recurrence

Support for daily dispatches at set hours, or specific weekly day selections (e.g. Tuesday and Thursday mornings) using interactive day pills.

Monthly Day-of-Month Clamping

For monthly dispatches on days 29–31, MarbleSend automatically clamps executions to the final valid day of shorter months (e.g. February 28/29).

DST & Timezone Awareness

Uses native PHP DateTimeZone calculations to guarantee broadcasts trigger at the intended local hour across Daylight Saving Time boundaries.

5.3 Automated RSS & Atom Feed Poller Pipeline

MarbleSend connects directly to your blog or content feed using native SimpleXML and cURL, converting new articles into automated subscriber campaigns without third-party integration tools.

Dual-Format Parser (RSS 2.0 & Atom 1.0)

Automatically inspects the XML root element, extracting titles, content summaries, permalinks, and publication timestamps with zero external libraries.

Live Pre-Flight Feed Diagnostic

The "Test Feed" button contacts the feed URL in real time, validating XML schema, verifying HTTP 200 responses, and previewing the latest 3 posts.

Deduplication & Initial Snapshot

Tracks last_rss_item_id and publish dates. On initial schedule creation, MarbleSend records the latest post ID as a baseline to prevent retroactively broadcasting historical archives.

5.4 Dynamic Audience Resolution & Recipient Targeting at Execution Time

A schedule's audience can also be limited to a group (combined with a tag, both must match). If that group is deleted the schedule is disabled instead of silently sending to nobody.

Unlike static campaign snapshots, scheduled broadcasts resolve their recipient lists dynamically at the exact second the occurrence triggers.

Real-Time Audience Expansion

Subscribers who register or receive tags minutes before the scheduled run are automatically included in the upcoming broadcast without manual list re-syncing.

Strict Deliverability Status Filter

The dispatch query enforces WHERE status = 'active', unconditionally excluding pending, unsubscribed, and suppressed contacts from recurring queues.

Tag-Based Segment Isolation

Supports broadcasting to all active contacts or targeting specific audience segments matching JSON tag arrays (e.g. #weekly-digest, #customers).

5.5 Occurrence Execution Logs, Run Audits & The Single Catch-Up Policy

Every automated run creates an immutable audit row in cm_schedule_occurrences, detailing execution timestamps, outcomes, recipient counts, and error telemetry.

3-State Occurrence Telemetry

Distinguishes between 'completed' (emails queued), 'skipped' (no new RSS articles detected), and 'failed' (SMTP disconnect, invalid template, or network timeout).

Single Catch-Up Idempotency Guard

If the cron worker was inactive across multiple missed runtimes, MarbleSend executes at most ONE catch-up broadcast upon recovery and advances next_run_at to the future.

Occurrence History Audit Modal

Click "View Execution History" from the sidebar to inspect past runs, recipient volumes, and diagnostic error logs directly within the UI.

5.6 On-Demand Manual Dispatches ("Run Now") & Emergency Overrides

Administrators can bypass the countdown timer and immediately trigger an unscheduled broadcast of any schedule using the "Trigger Send Now" action.

Immediate Queue Compilation

Instantly compiles the template, resolves active audience members, and dispatches batch items to cm_email_queue for the next worker cycle.

Preserved Recurrence Timeline

Triggering an on-demand broadcast does not shift or postpone the scheduled next_run_at timestamp, maintaining regular calendar cadence.

Diagnostic & Breaking News Utility

Ideal for testing end-to-end template formatting with live subscriber data or distributing urgent bulletins outside normal business hours.

5.7 Fail-Safe Worker Concurrency, Overlap Prevention & Non-Blocking File Locks

MarbleSend features an enterprise concurrency perimeter designed specifically to prevent duplicate broadcasts on budget shared hosting environments.

Non-Blocking flock Concurrency Lock

worker.php acquires an exclusive flock lock on storage/worker.lock. If a previous run is still sending over high-latency SMTP relays, subsequent invocations exit silently.

Dual-Worker Protocol Parity

Both CLI worker.php and HTTP cron.php share identical execution paths and locking mechanics, ensuring consistent behavior across cPanel cron and webhooks.

Atomic Occurrence Claims

Schedules due for execution are reserved using atomic PDO prepared statements, preventing duplicate queueing even in multi-threaded container environments.

5.8 Shared-Hosting Performance Tuning & The 8 Curated Optimization Tips

The dynamic Optimization Tip card on the Schedules page provides 8 actionable, production-proven operational recommendations for reliable shared-hosting delivery:

1. Optimal Morning Dispatch Windows

Schedule broadcasts on Tuesday or Thursday between 09:00 and 10:30 recipients' local time for peak open and click-through rates.

2. 1-Minute Cron Worker Precision

Execute worker.php every minute (* * * * *) with flock protection to guarantee sub-minute accuracy without process collision.

3. Atomic Queue Batch Claims

Query cm_email_queue using indexed prepared statements with LIMIT clauses to eliminate race conditions during high-volume spikes.

4. Subscriber Timezone Tag Segmentation

Segment contacts by region tags (#us-east, #apac, #emea) and assign dedicated staggered schedules to avoid delivering emails at 3:00 AM.

5. RSS Publishing Buffer Delay

Schedule RSS dispatches 1–2 hours after blog publication in your CMS, allowing CDN edge nodes and XML caches to fully update.

6. Routine Occurrence History Pruning

Periodically prune older execution logs from cm_schedule_occurrences to keep database table sizes lean and query indexes optimal on shared hosting.

7. 48-Hour Unopened Resend Strategy

Schedule a follow-up broadcast 48–72 hours later to subscribers where opened_at IS NULL with a fresh subject line to recapture 15–25% engagement.

8. Dedicated SMTP Profile Routing Isolation

Assign distinct SMTP profiles for automated RSS feeds versus high-volume marketing blasts to safeguard domain sender reputation.

5.9 News Posts Schedules (Substack-style Newsletters)

A Newsletter schedule can now send articles instead of one fixed template. Set Content source to “News posts” and each run delivers the next Ready posts from Templates → Posts, inserted into the template through {{news_posts}}. Nothing is sent when no post is waiting.

Which content source should I use?

Template only

The same template on every run — for recurring announcements or reminders. Smart Suppression is available.

RSS digest

New posts of an external blog feed. The first run only marks existing posts as seen; later runs send only new posts.

News posts

Articles you write inside MarbleSend. Each Ready post is sent once, oldest first. Best for a Substack-style newsletter.

Set it up in six steps

  1. 1Open Templates → Posts and write your article in Markdown.
  2. 2Create or open a template and place {{news_posts}} where articles should appear (the “News Posts Newsletter” starter already has it).
  3. 3Open Schedules → New, keep the type on Newsletter and set Content source to “News posts”.
  4. 4Choose the template, audience, SMTP profile, posts per email (1–20) and the cadence.
  5. 5Save. Only posts that become Ready after this moment can be sent by this schedule.
  6. 6Mark the post Ready. The next occurrence — or Run now — delivers it.

Which posts are sent on each run

At every occurrence the schedule picks posts by four rules, in this order.

Ready only

Drafts are ignored, however old they are.

After the cut-off

A post must have become Ready after the schedule's News posts mode was switched on. Your existing archive is never replayed.

Not sent by this schedule yet

Delivery is tracked per schedule, so two schedules can each send the same post once, but never twice.

Oldest first, up to the limit

“Posts per email” (default 1, maximum 20) caps how many are combined. The rest wait for the next run.

Example: you save the schedule on Monday. A post that became Ready on Friday is ignored; one that becomes Ready on Tuesday is sent at the next occurrence, then never again by this schedule.

What each run logs

Every occurrence appears in the schedule history. Posts are marked as delivered only when an email batch was really queued.

Sent

A Campaign was created and queued, for example “1 post · 5 recipients queued”. The posts are marked as sent by this schedule.

Skipped: no new posts

No Ready post is waiting. Nothing is sent and the schedule simply tries again at the next occurrence.

Skipped: no recipients

The audience was empty. The posts stay pending and are sent as soon as the audience has active contacts.

Failed

The template is missing or no longer contains {{news_posts}}. The posts stay pending; fix the template and run again.

Run now and test sends

Run now

A real occurrence, executed by the worker within about a minute. It sends the pending posts and marks them as delivered.

Send test digest

Sends the next pending posts — or, if none is pending, the newest Ready post — to one address through the schedule's own SMTP profile. It never marks anything as delivered and shares the 5-per-minute test limit.

Editing and pausing

  • Switching a schedule from Template only to News posts sets a new cut-off at that moment; switching back clears it.
  • Saving the schedule form re-checks that the template contains {{news_posts}}. Pause and Resume only flip the on/off switch and never re-check it, so you can always pause.
  • Smart Suppression is switched off automatically in News posts mode: it would exclude everyone who received an earlier issue.
  • News posts is available for Newsletter schedules only; RSS schedules keep their own feed logic.

Reading the schedule screens

  • The schedule list labels these schedules “News posts” instead of “Newsletter”.
  • The detail panel shows how many Ready posts are waiting for this schedule.
  • In the form, templates without {{news_posts}} are flagged, and a warning appears if you pick one.

Quick troubleshooting

SymptomWhat to check
My Ready post was not sentCheck that it became Ready after the schedule was saved (or switched to News posts), that the schedule is enabled, and look at the history entry for “no recipients” or “failed”.
The schedule says “no new posts” right after I wrote oneThe post may still be a Draft, or it was already sent by this schedule. Open Templates → Posts and check the badge and “Sent N×”.
I cannot save: the template must contain {{news_posts}}Add the placeholder to the template body (not only to the subject) or choose another template.
I want to resend an old postCopy its text into a new post and mark that one Ready — a schedule never sends the same post twice.
Module 06

Settings & Infrastructure (/settings)

SMTP multi-relay management, cron heartbeat, 23 languages, deliverability diagnostics, and license activation.

Settings & Infrastructure UI Inspector

Click any component below to view database schema impact, background processes, and operational guidelines

Interactive Inspector
SMTP

SMTP Profiles

BYO SMTP & Managed SES Relay
WORKER

Worker & Cron

1-minute execution cadence
LOCALES

23 Locales & Theme

RTL mirror & Dark/Light mode
API KEYS

REST API Keys

Scoped developer access
WEBHOOKS

Event Webhooks

HMAC-SHA256 event push
DELIVERABILITY

Deliverability Suite

SPF, DKIM, DMARC & BIMI
AI BYOK

AI Providers

BYOK & MarbleSend Credits
LICENSE

Ed25519 License

Cryptographic grant token

Database Impact:

Support Team Pro-Tip:

6.1 Dual Delivery Architecture: BYO SMTP & Managed Amazon SES Relay

MarbleSend decouples campaign execution from delivery relays with two robust transport models: (1) Bring Your Own (BYO) SMTP for connecting your existing mail servers, and (2) Managed Amazon SES Relay via admin.marblesend.com for instant cloud delivery with zero AWS setup. Supports Option 1 Friendly From (0 DNS configuration) and Option 2 Custom Domain Easy DKIM with dynamic pricing and atomic credit management.

Option 1: BYO SMTP (Custom Mail Infrastructure)

Connect any standard SMTP provider including Postmark, Mailgun, Brevo, SendGrid, Resend, or your host's cPanel mailbox. Supports Port 587 with STARTTLS, Port 465 with implicit SSL/TLS, and Port 25 for local relays, with custom authentication and isolated sender headers.

6.1.1 SMTP Server Providers (Free, Commercial & Shared Hosting)

SMTP Directory & Comparison

Connect your own mail transport with standard SMTP credentials. Choose between high-volume dedicated cloud relays or your existing hosting mailboxes, keeping strict sending quotas in mind.

Important Transparency Notice: Shared Hosting Sending Limits

Standard shared hosting mailboxes (cPanel, Webmail) enforce strict hourly and daily rate limits (typically 100–500 emails/day, and script-based php mail() is often throttled to 100/day or 10/min). Sending newsletters or broadcast campaigns through standard hosting mailboxes can cause 'Quota Exceeded' bounce rejections or account suspensions. For campaign marketing, upgrade to dedicated Business Email / Email Marketing packages or connect a cloud relay.

Optimized for high-volume dispatches, robust IP reputation management, and high deliverability.

Provider / Website Free Allowance Commercial Pricing SMTP Host & Port Key Rules & Limitations
Amazon SES (Direct) Free tier on EC2 only (62k/mo); Otherwise pay-as-you-go $0.10 / 1,000 emails ($1 per 10k) email-smtp.<region>.amazonaws.com
Port 587 (TLS) or 465 (SSL)
Requires AWS account setup, IAM SMTP user credentials, and sandbox exit request.
Brevo 300 emails / day (Free forever) Starter from ~$9/mo (5k emails) to ~$18/mo (20k emails) smtp-relay.brevo.com
Port 587 (TLS)
Free tier resets daily. Great for low-volume testing without entering a credit card.
Mailgun (Sinch) Trial (5,000 emails for 30 days) Foundation: $35/mo (50,000 emails); Scale: $90/mo smtp.mailgun.org (or smtp.eu.mailgun.org)
Port 587 (TLS) or 465 (SSL)
Robust analytics and deliverability. Supports EU data residency.
Twilio SendGrid 100 emails / day (Free forever) Essentials: ~$19.95/mo (50k emails); Pro: ~$89.95/mo smtp.sendgrid.net
Port 587 (TLS) (User: apikey)
Requires Two-Factor Authentication. Username must literally be apikey.
Postmark (ActiveCampaign) Free Developer trial: 100 emails/mo From $15/mo (10,000 emails), $55/mo (50,000 emails) smtp.postmarkapp.com
Port 587 (TLS) or 2525
Industry-leading transactional speed and strict spam filters. Separate broadcast streams required.
Resend 3,000 emails / month (100 / day free) Pro: $20/mo (50,000 emails/mo) smtp.resend.com
Port 465 (SSL) or 587 (TLS) (User: resend)
Modern developer experience, clean UI, quick domain verification via DNS.

Standard cPanel and hosting mailboxes suitable for light correspondence, but strictly capped for mass marketing.

Host: smtp.hostinger.com | Port 465 (SSL) / 587 (TLS)
Business Email Plans (Per Mailbox)
Free Business Email Rp0 / mo

1 GB storage, 1 forwarding rule, 1 alias, up to 100 mailboxes/domain. Standard webmail & antispam. Strict sending cap (~100-200/day).

Starter Business Email Rp7.900 / mo

5 GB storage, 5 forwarding rules, 5 aliases, expandable mailbox storage. No mailbox limit per domain.

Standard Business Email Rp10.900 / mo

20 GB storage, 20 forwarding rules, 10 aliases, AI Kodee mailbox assistant & smart replies.

Premium Business Email Rp19.900 / mo

50 GB storage, 50 forwarding rules, 30 aliases, full AI suite & priority deliverability.

Hostinger Reach (Email Marketing)
Starter Marketing Rp23.900 / mo

100 recipients, 200 emails / month. Suitable for basic transactional testing.

Grow Marketing Rp36.900 / mo

500 recipients, 500 emails / month, 10 AI credits, remove Reach branding, automations.

Pro Marketing Rp54.900 / mo

1,000 recipients, 7,000 emails / month, 20 AI credits, custom HTML code, send-time optimization.

Namecheap Jellyfish / cPanel

• cPanel Shared: Max ~500 emails/hour per cPanel domain.
• Private Email: Starter (~$1.24/mo per mailbox, 5GB). Pro (~$2.82/mo, 30GB).
• SMTP Host: mail.privateemail.com (Port 465 SSL or 587 TLS).

View Namecheap Email
Bluehost cPanel / M365

• Shared cPanel: Default cap ~500 emails/hour. Internal mail system may defer large sudden bursts.
• Professional Email: Integrated with Microsoft 365 or Google Workspace ($2.99–$9.99/mo).
• SMTP Host: box###.bluehost.com or mail.yourdomain.com (Port 465 SSL).

View Bluehost Email
SiteGround Site Tools

• Site Tools Mail: Included in hosting (StartUp, GrowBig, GoGeek).
• Rate Limits: ~400 emails/hour on StartUp; up to ~800/hour on GoGeek. Strictly intended for transactional mail.
• SMTP Host: mail.yourdomain.com (Port 465 SSL).

View SiteGround Email
DreamHost Custom Panel

• Paid Business Email: ~$1.99/mo per mailbox (25GB storage).
• Strict Outbound Policy: Maximum 100 recipients per hour. Mass marketing prohibited via shared SMTP.
• SMTP Host: smtp.dreamhost.com (Port 465 SSL or 587 TLS).

View DreamHost Email
HostGator cPanel

• cPanel Limit: Max 500 emails/hour per domain on shared plans.
• Workspace Option: Google Workspace addon ($6–$18/mo).
• SMTP Host: mail.yourdomain.com (Port 465 SSL or 587 TLS).

View HostGator Email
GoDaddy Microsoft 365

• M365 Reseller: Email Essentials from ~$1.99–$5.99/mo (10GB-50GB).
• SMTP Relay Note: Direct SMTP AUTH requires enabling Authenticated SMTP in the M365 Admin Portal.
• SMTP Host: smtp.office365.com (Port 587 TLS).

View GoDaddy Email

Select the appropriate port and security protocol based on your server network and firewall rules.

Port 587 (STARTTLS) STANDARD

The modern standard for email client-to-relay submission (RFC 6409). Starts in cleartext and immediately upgrades connection to TLS. Supported by almost all cloud relays.

Port 465 (SSL / SMTPS) ENCRYPTED

Implicit TLS/SSL wrapper connection (RFC 8314). Required by Hostinger, Namecheap cPanel, and selected enterprise relays. Highly reliable when hosting firewalls restrict 587.

Port 25 (Plain / MTA) BLOCKED

Server-to-server MTA routing. Almost universally blocked on cloud hosts (Hostinger, AWS, DigitalOcean, Hetzner, etc.) to combat spam. Do NOT use for outbound SMTP credentials.

6.1.1 Managed Amazon SES Relay & Custom Sender Domains

MANAGED SES

Enterprise cloud delivery infrastructure managed by MarbleSend via api.marblesend.com. Eliminates SMTP port blocking, IAM credential management, and IP reputation risks on shared hosting. Features dual sender modes (Friendly From & Easy DKIM), free starter credits, atomic ledger auditing, and dynamic on-demand scaling.

Option 2: Managed Amazon SES Relay (Edge sigv4)

Enterprise Amazon SES cloud delivery pre-configured and managed by MarbleSend via admin.marblesend.com. Requires zero AWS account setup, zero IAM credential management, and no IP warming. Top up credits on-demand with automatic meter sync and real-time quota visibility.

SES Opsi 1: Friendly From (Zero DNS Setup)

Instant campaign delivery with zero DNS configuration. Emails are enveloped via relay@marblesend.com displaying your friendly brand name, while recipient replies route directly to your personal mailbox.

SES Opsi 2: Custom Domain & Easy DKIM (3 CNAMEs)
Easy DKIM

Send directly with From: "Your Brand" <you@yourdomain.com>. Add 3 CNAME DNS records generated by AWS SES v2 to authenticate domain ownership and achieve maximum inbox placement.

Custom Domain Quotas & Dynamic Pricing Catalog

Each active license includes 1 custom sender domain identity free. Additional domain slots and SES credit packs (10k, 50k, 100k, 250k bulk) are priced dynamically from admin.marblesend.com without needing app updates.

Atomic Credit Ledger, Idempotency & Auto-Refund

Every email dispatched via MarbleSend SES reserves 1 credit atomically using queue-item UUID idempotency (cm_addon_ledger.uq_addon_ledger_key). If relay transmission fails, the reservation is instantly released and refunded without manual intervention.

6.1.2 Sending rules with the SES Relay

Amazon SES only works for senders who keep their lists clean, so these rules are enforced by the MarbleSend relay itself, not just by this app. They cannot be switched off and they apply to every email sent through the SES Relay.

Sender identity

Before the first send, complete Settings → SMTP → Sender Identity & Acceptable Use: legal name, postal address, country, phone number and acceptance of the Acceptable Use Policy. Sending stays blocked until it is saved. Your name and address appear in every email footer; the phone number is never shown.

Unsubscribe in every email

Every campaign body must contain the {{unsubscribe_link}} tag, otherwise the send is refused. On top of that, the relay adds the List-Unsubscribe and one-click headers and a footer with your name, address, an unsubscribe link and “Sent with MarbleSend”. The footer cannot be removed.

Permission-based lists

While the SES Relay is in use, signup pages and the API always use double opt-in, public signup requires Cloudflare Turnstile, and a CSV import needs a tick confirming that everyone gave permission. Someone who unsubscribed or was suppressed cannot re-subscribe through a public form.

Bounces and complaints

Hard bounces and spam complaints reach MarbleSend automatically and turn the contact into suppressed. The relay also refuses to send to a suppressed address, and no credit is used. Soft bounces are retried and only suppress a contact after repeated failures.

Limits and automatic pause

Each account has a daily sending limit (2,000 emails by default). If bounce or complaint rates get too high, sending pauses automatically until support reviews the account, and it also stops if the license is inactive. A banner in Settings → SMTP explains why, and queued emails wait instead of being lost.

Authenticated sending

Emails are signed with DKIM for marblesend.com (or for your own domain once verified). The return path uses mail.marblesend.com, so SPF aligns and DMARC passes. Replies go to the Reply-To address you set.

Easy DKIM Provider Playbook

Easy DKIM 3 CNAME Setup Directory (Multi-Provider)

Comprehensive DNS configuration guide for adding the 3 AWS SES Easy DKIM CNAME records across popular hosting providers and domain registrars, preventing common syntax pitfalls and ensuring fast verification.

Universal DNS Architecture & Essential Rules

Easy DKIM delegates cryptographic signing to Amazon SES via 3 CNAME records. Because AWS manages and automatically rotates the underlying 1024/2048-bit RSA keys without requiring manual DNS updates, CNAME records must remain in place permanently.

Record Type Host / Name Value / Target / Points To TTL
CNAME <token1>._domainkey.yourdomain.com <token1>.dkim.amazonses.com 300 (or Auto)
CNAME <token2>._domainkey.yourdomain.com <token2>.dkim.amazonses.com 300 (or Auto)
CNAME <token3>._domainkey.yourdomain.com <token3>.dkim.amazonses.com 300 (or Auto)
RULE 1
Rule 1: Beware of the "Double Domain" Trap

Most domain registrars and web hosts automatically append your root domain to the Host/Name field. If you paste "token._domainkey.yourdomain.com", the DNS server will resolve "token._domainkey.yourdomain.com.yourdomain.com", causing DKIM verification to fail immediately. Always enter only "token._domainkey" unless your provider explicitly requires the full FQDN.

RULE 2
Rule 2: CNAME, Not TXT

Unlike manual DKIM which uses a TXT record with a static public key (p=MIGf...), Amazon SES Easy DKIM uses 3 CNAME records pointing directly to token.dkim.amazonses.com. Do not select TXT as the record type.

RULE 3
Rule 3: Proxying & Cloudflare Grey Cloud

If you use Cloudflare or an HTTP proxy, the 3 DKIM CNAME records must be set to DNS Only (Grey Cloud / Unproxied). HTTP proxying hides the CNAME target and breaks cryptographic email verification.

DNS Verification Command
# Check resolution via dig (macOS / Linux):
dig CNAME <token1>._domainkey.yourdomain.com +short

# Check resolution via nslookup (Windows / PowerShell):
nslookup -type=CNAME <token1>._domainkey.yourdomain.com
Hostinger (hPanel DNS Zone)
hPanel DNS
Hostinger hPanel → Domains → Select Domain → DNS / Nameservers
Record Type Host / Name Value / Target / Points To TTL
CNAME <token>._domainkey (hPanel auto-appends .yourdomain.com) <token>.dkim.amazonses.com 300 (or 14400)
1

Log in to your Hostinger hPanel dashboard and navigate to Domains.

2

Select your domain and click DNS / Nameservers in the left sidebar.

3

Under Manage DNS Records, select CNAME from the Type dropdown.

4

In the Name field, enter only token._domainkey (hPanel automatically appends .yourdomain.com).

5

In the Target field, enter token.dkim.amazonses.com and set TTL to 300 or default (14400).

6

Click Add Record. Repeat the process for all 3 tokens provided in MarbleSend.

Hostinger hPanel displays your domain grayed out next to the input box. Do not append your domain name or an extra dot in the Name field.
DreamHost (DreamHost Web Panel)
DreamHost Web Panel
DreamHost Panel → Websites → Manage Websites → DNS Settings (or Domains → Manage Domains → DNS)
Record Type Host / Name Value / Target / Points To TTL
CNAME <token>._domainkey (DreamHost appends .yourdomain.com) <token>.dkim.amazonses.com Default
1

Log in to the DreamHost Web Panel and navigate to Websites > Manage Websites.

2

Locate your domain, click the three dots or DNS Settings (or Domains > Manage Domains > DNS).

3

Scroll down to the "Add a custom DNS record" section and click Add Record.

4

In the Name field, enter token._domainkey (DreamHost shows .yourdomain.com automatically).

5

Set Type to CNAME and enter token.dkim.amazonses.com in the Value field.

6

Click Save or Add Record. Repeat for all 3 tokens.

DreamHost DNS updates can take up to 1–2 hours to fully synchronize across their internal nameserver cluster.
Namecheap (Advanced DNS)
Advanced DNS
Namecheap Dashboard → Domain List → Manage → Advanced DNS tab
Record Type Host / Name Value / Target / Points To TTL
CNAME Record <token>._domainkey (never paste root domain here) <token>.dkim.amazonses.com Automatic (or 1 min)
1

Log in to Namecheap, open Domain List from the left sidebar, and click Manage next to your domain.

2

Switch to the Advanced DNS tab at the top of the management page.

3

In the Host Records section, click the red ADD NEW RECORD button.

4

Select CNAME Record from the Type dropdown menu.

5

In the Host field, enter ONLY token._domainkey (never include your domain name).

6

In Target, enter token.dkim.amazonses.com, set TTL to Automatic (or 1 min), and click the green checkmark ✓ to save. Repeat for all 3 records.

If your domain uses Custom DNS pointing elsewhere, this tab will be disabled; add records in your active DNS provider instead.
Bluehost (Account Manager / Zone Editor)
Bluehost DNS
Bluehost Portal → Domains → DNS (or cPanel → Zone Editor)
Record Type Host / Name Value / Target / Points To TTL
CNAME <token>._domainkey (Bluehost handles suffix automatically) <token>.dkim.amazonses.com 4 Hours (or Default)
1

Log in to Bluehost Account Manager, click Domains in the left navigation, and choose your domain.

2

Navigate to the DNS tab (or open cPanel Zone Editor if using legacy hosting).

3

Scroll to the CNAME (Alias) section and click Add Record.

4

In the Host Record field, enter token._domainkey.

5

In Points To, enter token.dkim.amazonses.com and select TTL (4 Hours or Default).

6

Click Save. Repeat for the remaining 2 CNAME tokens.

Bluehost automatically handles the domain suffix in the background. Never input the full domain into the Host Record box.
SiteGround (Site Tools DNS Zone Editor)
Site Tools DNS
SiteGround Client Area → Websites → Site Tools → Domain → DNS Zone Editor
Record Type Host / Name Value / Target / Points To TTL
CNAME <token>._domainkey (Site Tools appends .yourdomain.com) <token>.dkim.amazonses.com 1 hour (3600)
1

Log in to SiteGround Client Area, go to Websites, and open Site Tools for your domain.

2

In the left sidebar, expand Domain and click DNS Zone Editor.

3

Select the CNAME tab under Create New Record.

4

In the Name field, enter token._domainkey (Site Tools appends .yourdomain.com automatically).

5

In the Resolves To field, enter token.dkim.amazonses.com and leave TTL at 1 hour (3600).

6

Click Create. Repeat for all 3 tokens.

SiteGround nameservers propagate in seconds. Ensure there are no old conflicting TXT records on the same selector subdomain.
HostGator (cPanel Zone Editor)
cPanel Zone Editor
HostGator Portal → cPanel → Domains → Zone Editor
Record Type Host / Name Value / Target / Points To TTL
CNAME <token>._domainkey (cPanel automatically appends dot) <token>.dkim.amazonses.com 14400 (or 3600)
1

Log in to HostGator Customer Portal and launch cPanel.

2

Under the Domains section, click Zone Editor.

3

Find your domain and click + CNAME Record (or click Manage > Add Record).

4

In the Name field, enter token._domainkey (cPanel auto-appends your domain with a trailing dot).

5

In the CNAME / Record field, enter token.dkim.amazonses.com.

6

Click Add a CNAME Record. Repeat for all 3 tokens.

In cPanel Zone Editor, verify in the list that the record is named token._domainkey.yourdomain.com. without accidental duplication.
GoDaddy (Domain Portfolio DNS)
GoDaddy DNS
GoDaddy Account → My Products → Domains → DNS (or Manage DNS)
Record Type Host / Name Value / Target / Points To TTL
CNAME <token>._domainkey (never include root domain in GoDaddy) <token>.dkim.amazonses.com 1/2 Hour (or Default)
1

Log in to your GoDaddy account and go to My Products.

2

Under Domains, find your domain and click DNS (or the three dots > Manage DNS).

3

In the DNS Records section, click Add New Record.

4

Select CNAME from the Type dropdown menu.

5

In the Name field, enter ONLY token._domainkey (do NOT include .yourdomain.com).

6

In Value, enter token.dkim.amazonses.com, set TTL to 1/2 Hour (or Default), and click Save. Repeat for all 3 records.

GoDaddy automatically appends your domain name. Entering the full domain will cause a DNS syntax error or create an invalid double-domain record.
Verification & Propagation

After saving the 3 CNAME records, DNS propagation typically takes 5–30 minutes (up to 24–48 hours depending on TTL). Return to MarbleSend Settings > SMTP, open the custom domain card, and click "Verify DNS". Once validated by Amazon SES, the status badge will switch to Verified.

6.2 Worker Background Heartbeat, 1-Minute Cron & Concurrency Guard

MarbleSend sends email in the background. A cron job is a timer in your hosting panel that wakes MarbleSend up every minute so it can run schedules and autoresponders, retry failed deliveries, collect bounces and keep a long Campaign going. A Campaign you send starts at once, but everything that has to happen later depends on this timer. If Settings → System shows Cron Not Detected, the timer is missing, is not running every minute, or points to the wrong place. The three steps below fix that.

Step 1: Copy your cron line

Open Settings → System, find Cron Job Command and click Copy Command. The line already holds the right folder and, when your server tells us, the right PHP. Use it exactly as it is; the examples further down this page only show the shape of a line. If Settings says it could not detect PHP, ask your host for the PHP command-line path and replace the word php at the start of the line.

Step 2: Add it in your hosting panel

Pick your host below. Menu names change from time to time, so look for the closest match. Whatever the panel, the job must run every minute (* * * * *) and its command is the line from Step 1. Plesk, DirectAdmin and other panels call it Scheduled Tasks or Cron Jobs.

Hostinger (hPanel)
  1. Open hPanel → Websites → Dashboard for your site, then click Cron Jobs in the sidebar.
  2. Set Type to Custom. (The PHP type only takes the path of a .php file; your line also contains the PHP command.)
  3. Paste the line from Step 1 into Command to run.
  4. Set the schedule to every minute, or enter * * * * *.
  5. Click Save. The job appears under List of Cron Jobs, where you can also read its last output.

Hostinger runs cron jobs in UTC. The Single plan allows 2 cron jobs; Premium and higher plans have no limit.

Namecheap

Open cPanel from your Namecheap hosting account, then follow these steps.

  1. Open cPanel and, in the Advanced section, click Cron Jobs.
  2. Under Add New Cron Job, choose Once Per Minute (* * * * *) in Common Settings.
  3. Paste the line from Step 1 into Command.
  4. Click Add New Cron Job. The job runs every minute from now on.

Namecheap does not allow cron jobs that run more often than every 5 minutes, or more than 5 cron jobs, on shared servers. Use Web-Cron below if you need every minute.

Bluehost
  1. Log in to the Bluehost Portal, click Websites, then Manage Site for your site.
  2. Open the Advanced tab, find Cron Jobs and click Manage, then Add Cron.
  3. Choose a frequency in Common Settings (or fill in Minute, Hour, Day, Month and Weekday).
  4. Paste the line from Step 1 into Command and click Add.

Bluehost's own documentation says shared hosting only runs cron jobs every 15 minutes or longer (one page says 5 minutes). Use Web-Cron below if you need every minute.

HostGator

In the Customer Portal open Websites → Manage Site → Advanced, then under Cron Jobs click Manage and Add Cron. The form has the same fields as cPanel, so the steps below apply. Or open cPanel → Advanced → Cron Jobs.

  1. Open cPanel and, in the Advanced section, click Cron Jobs.
  2. Under Add New Cron Job, choose Once Per Minute (* * * * *) in Common Settings.
  3. Paste the line from Step 1 into Command.
  4. Click Add New Cron Job. The job runs every minute from now on.

HostGator's help pages do not state the shortest interval allowed on shared hosting. If every minute is refused, use Web-Cron below.

GoDaddy

On your GoDaddy product page, next to Linux Hosting click Manage, then cPanel Admin, then follow these steps.

  1. Open cPanel and, in the Advanced section, click Cron Jobs.
  2. Under Add New Cron Job, choose Once Per Minute (* * * * *) in Common Settings.
  3. Paste the line from Step 1 into Command.
  4. Click Add New Cron Job. The job runs every minute from now on.

GoDaddy's help pages do not state the shortest interval allowed. If every minute is refused, use Web-Cron below.

DreamHost
  1. Open the DreamHost panel, go to Cron Jobs and click Add New Cron Job.
  2. Choose a user (an existing SFTP or shell user), give the job a title, and optionally an email address for its output.
  3. Paste the line from Step 1 into Command. DreamHost keeps PHP at paths such as /usr/local/php83/bin/php (use your PHP version).
  4. Set the timing and turn on locking so two runs never overlap, then save.

In custom timing the panel lets you pick at most 12 different minutes per hour, so the shortest regular schedule you can build is every 5 minutes. Times are in Pacific time. Use Web-Cron below if you need every minute.

SiteGround
  1. Open Site Tools → Devs → Cron Jobs.
  2. Choose the interval from the list (or set minute, hour, day, month and weekday).
  3. Paste the line from Step 1 into Command. An email address for the output is optional.
  4. Click Create.

SiteGround's documentation shows presets such as Twice Per Hour and does not state the shortest interval. If your plan has no every-minute option, use Web-Cron below.

No cron access, or your host limits it? Use Web-Cron
  1. Open Settings → System and copy the Web-Cron URL. Keep it private: it contains your secret token.
  2. Create a free account at a cron service such as cron-job.org.
  3. Create a job, paste the URL and set it to run every minute. Save and activate it.
  4. Use either Web-Cron or a cron job from your host, not both: one is enough.
Step 3: Check that it works

Wait one or two minutes, then reload Settings → System. The badge should say Active and Last Heartbeat should show a time from the last minute. Site Health on the same page should show the worker as Passed. If your host only allows a job every 5 or 15 minutes, the badge can switch between Active and Cron Not Detected, because it expects a run within the last 5 minutes; email still goes out whenever the worker runs.

Still showing Cron Not Detected?
  • The PHP command is wrong. Ask your host for the PHP command-line path (for example /opt/alt/php83/usr/bin/php on Hostinger or /usr/local/bin/php on many cPanel hosts) and replace php at the start of the line.
  • Only part of the line was pasted. Paste the whole line, including >/dev/null 2>&1 at the end.
  • The folder is wrong. worker.php is in the folder that contains public, not inside public.
  • To see the error, run the job once without >/dev/null 2>&1 and read its output. A working worker prints a line like worker.php: processed=0.
  • The job is for another site on the same account. Check the folder in the command.
  • Give it up to 5 minutes after saving before you check again.

For technical users

Email compilation, audience query resolution, delivery dispatch, and webhook processing operate asynchronously via a 1-minute background worker. MarbleSend employs a dual-entrypoint architecture (native CLI worker.php or HTTP cron.php) protected by non-blocking file locks to eliminate overlapping executions on resource-constrained shared hosting.

Native CLI Crontab (Option A)
* * * * * php /home/USER/your-folder/worker.php >/dev/null 2>&1

Executes directly from the operating system shell (* * * * * php /path/to/worker.php >/dev/null 2>&1). Completely bypasses HTTP timeout and gateway limits, making it the ideal choice on cPanel, Plesk, and DirectAdmin.

HTTP Web-Cron Fallback (Option B)
GET https://your-domain.com/cron.php?token=SECURE_TOKEN

For hosting environments without shell crontab access, pinging GET /cron.php?token=SECURE_TOKEN via third-party services (e.g. cron-job.org or UptimeRobot) triggers the exact same queue processing safely.

Non-Blocking flock Concurrency Guard

The worker locks storage/worker.lock with flock(LOCK_EX | LOCK_NB) on start. If a previous run is still processing a heavy queue, subsequent ticks exit cleanly without race conditions or memory exhaustion.

Atomic Queue Claims & Backoff

Claims batches of 50 queue items atomically using indexed status queries. Failed deliveries incur exponential backoff with retry counters up to max_attempts before transitioning to failed.

6.3 Global 23 Locales, RTL Arabic & Admin Security Machine

MarbleSend is engineered as an internationally accessible self-hosted platform. It ships with 23 complete human-curated native language dictionaries, automatic Right-to-Left layout mirroring for Arabic, persistent tri-state theme customization, and an impenetrable single-tenant security perimeter.

Zero-Dependency 23 Native Locales

Language files in app/lang/*.php are pure PHP arrays loaded into memory in sub-millisecond execution time, requiring zero gettext server binaries or external translation API dependencies.

Bidirectional RTL Layout Mirroring

Selecting Arabic (ar) automatically injects dir='rtl' into the HTML document root, mirroring navigation sidebars, table columns, drawer panels, and chevron iconography across the entire UI.

Persistent Tri-State Theme Engine

Allows seamless switching between Dark Palette, Light Palette, and System OS preference. The active selection is saved to cookies (marblesend_theme) to guarantee zero layout shift on page load.

Single-Admin Hardened Security

Admin credentials stored in cm_settings.admin_user are encrypted with bcrypt work factor 12. Protects against session fixation via automatic session ID regeneration on login.

6.3.1 Two-Factor Authentication (TOTP) & QR Code Setup

Two-factor authentication adds a second lock to the admin account: after the password, MarbleSend asks for a 6-digit code that changes every 30 seconds and that only your phone can generate. It is optional, takes about a minute to enable, and works with any standard authenticator app such as Google Authenticator, Microsoft Authenticator, 1Password, Bitwarden, Authy or Aegis. You will find it at Settings → Language & Account → Two-factor authentication (TOTP).

How it works

MarbleSend uses the open TOTP standard (RFC 6238: HMAC-SHA1, 6 digits, 30-second steps). Enabling it creates a random secret that is shared once with your app. From then on, signing in takes the password and then a current code. A code that was already used cannot be replayed, and the second step expires after 5 minutes.

Enable it step by step

1) Open Settings → Language & Account and press Enable. 2) In your authenticator app choose “Scan QR code” and scan the square shown on screen. 3) Type the 6-digit code your app now shows and press “Confirm and enable”. 4) Save the eight recovery codes that appear once: copy them into a password manager or print them.

The QR code stays on your server

The QR image is drawn by your own installation as a small vector graphic; the secret is never sent to an online QR generator or any other third party. If you cannot scan (for example when the app is on the same phone), use the setup key or the otpauth link shown under the QR code. The secret is stored encrypted (AES-256-GCM) with your ENCRYPTION_KEY, like every other credential.

Signing in with a code

After the correct password you are asked for the 6-digit code. Wrong codes are rate-limited per account, so guessing is not practical. If codes are rejected even though you type them correctly, check that the phone’s date and time are set to automatic; a wrong clock produces wrong codes.

Recovery codes

Eight one-time codes are created when you enable 2FA. Each one works once in the code field instead of the 6-digit code. They are stored only as hashes, so they cannot be displayed again; use “New recovery codes” (it needs a current code) to replace the whole set, which invalidates the old ones.

Lost phone and no recovery codes

The emailed link for a forgotten password does not skip two-factor authentication, on purpose, so it cannot help here. On the server, run “php tools/reset-admin.php” (SSH or your hosting terminal): it resets the admin password, clears lockouts, ends every session and turns 2FA off, and you can then enable it again with the new device. To turn it off normally, use Disable in the same card (password plus a current or recovery code).

6.3.2 Signup Protection (Cloudflare Turnstile): Get Your Free Keys

Turnstile is Cloudflare’s free, privacy-friendly alternative to CAPTCHA. When it is enabled, your public signup pages (the /s/your-page forms and /subscribe) check that the visitor is a real person before a subscription is accepted, which stops bots from flooding your list and your confirmation emails. It works on top of the built-in honeypot and the per-IP and per-address limits, and it is optional, except while you send through the SES Relay (see the last note). You need two keys, a Site key (public) and a Secret key (private), from your own free Cloudflare account; your domain does not have to use Cloudflare for DNS.

1 Create or sign in to a Cloudflare account

Go to dash.cloudflare.com and sign up (it is free) or log in. You do not need to add your website to Cloudflare or change your DNS in order to use Turnstile.

2 Open Turnstile and add a widget

In the dashboard menu open “Turnstile” (or search for it), then press “Add widget” (older dashboards say “Add site”). Menu names change from time to time; look for Turnstile under Application security.

3 Name the widget and list your hostnames

Give it any name, for example “MarbleSend signup”. Under Hostname add the domain where MarbleSend runs, without https:// or a path, for example app.yourdomain.com. If your signup pages are shown on another domain, add that one too. Add localhost only while you test on your own computer.

4 Choose the widget mode

Choose Managed (recommended): most visitors pass without any puzzle and Cloudflare asks for a quick click only when a visit looks suspicious. Non-interactive shows a short progress bar without any click, and Invisible shows nothing at all. Leave pre-clearance off, then press Create.

5 Copy the two keys

Cloudflare shows a Site key (public) and a Secret key (private). Copy both. The secret is used only by your server to confirm each visitor with Cloudflare, so never paste it anywhere public. You can view or rotate it later in the widget’s settings.

6 Paste them into MarbleSend

Open Settings → Language & Account → Signup protection (Cloudflare Turnstile), paste the Site key and the Secret key, and press Save. The badge changes to “Protection is ON”. To turn it off later, clear both fields and save. The saved secret is stored encrypted and never shown again; leave the field blank to keep it.

7 Test it

Open one of your public signup pages in a private browser window: the Turnstile widget appears above the button. Subscribe with a test address and you should receive the confirmation email. A submission that has not passed the check is rejected.

When Cloudflare is unreachable

If your server cannot reach Cloudflare’s verification service (an outage or a blocked outbound connection), MarbleSend normally lets the signup through and logs the event instead of locking every visitor out; the honeypot and the per-IP and per-address limits still apply. While you send through the SES Relay it does the opposite and fails closed: signups are refused until Cloudflare is reachable again.

Test keys for development

Cloudflare publishes dummy keys for testing: Site key 1x00000000000000000000AA with Secret key 1x0000000000000000000000000000000AA always passes. They never protect a real site, so replace them with your own keys before going live.

Troubleshooting

The widget does not appear: check that the hostname of the page is listed in the Turnstile widget and that no browser extension blocks challenges.cloudflare.com. “Invalid site key”: the Site key was copied with a space or belongs to another widget. Real visitors are rejected: paste the Secret key again; it must belong to the same widget as the Site key.

Required while you use the SES Relay

If you send through the Managed Amazon SES Relay, Turnstile is mandatory for public signup forms. Until both keys are saved, the signup page shows “sign-up is temporarily unavailable” and Settings → Language & Account shows a notice with a button that takes you to the right card. In that mode every new subscriber must also confirm by email (double opt-in), even if a signup page or an API call asks for single opt-in.

6.4 Scoped REST API Keys & Developer Programmatic Access

The REST API allows external websites, landing pages, mobile apps, and the official MarbleSend Forms WordPress plugin to securely interact with your contact database and broadcast engine. API keys use cryptographic prefixing, one-way SHA-256 storage, and granular capability scoping.

Token Generation & Write-Only Display

Keys are generated with an ms_ prefix followed by 32 cryptographically secure random bytes via random_bytes(24). The raw key is revealed only once during creation and can never be retrieved again.

One-Way SHA-256 Hash Storage

Only the SHA-256 hash (hash('sha256', $rawKey)) is persisted in cm_api_keys.key_hash. Even if the database is read directly, active tokens cannot be decrypted or recovered.

Granular Capability Scopes

Restrict keys to specific capabilities: contacts:read, contacts:write, campaigns:read, campaigns:write, campaigns:send, schedules:read, and schedules:write for tight least-privilege control.

Bearer Authentication & Audit Logs

Incoming requests authenticate via Authorization: Bearer ms_... headers evaluated using hash_equals(). Telemetry logs record the exact last_used_at timestamp on every successful API call.

6.5 Outbound Event Webhooks & HMAC-SHA256 Cryptographic Delivery

Contact events (subscribed, confirmed, unsubscribed, suppressed) now carry a groups array of {id, name} next to tags.

Webhooks provide real-time HTTP event streaming to external endpoints, allowing downstream CRM syncs, Slack notifications, or custom automation pipelines to react instantly to subscriber opt-ins, campaign broadcasts, unsubscribes, and hard bounces.

Lifecycle Event Subscriptions

Subscribe to critical lifecycle triggers: contact.created, contact.unsubscribed, campaign.sent, campaign.bounced, and link.clicked with individual webhook toggling.

HMAC-SHA256 Signature Header

Every webhook POST request includes X-MarbleSend-Signature: sha256=... generated using hash_hmac('sha256', $payload, $secret), allowing receiving servers to verify authenticity.

Circuit Breaker & Retry Mechanism

Failed webhook deliveries (HTTP 5xx or connection timeouts) trigger exponential backoff retries. Endpoints with consecutive failures are automatically paused to prevent server overhead.

Delivery Audit History & Test Ping

The Test Webhook button sends mock payloads immediately. The delivery history drawer displays HTTP response status codes, execution latency in milliseconds, and error messages.

6.6 The 4-Pillar Deliverability Suite (SPF, DKIM, DMARC, BIMI)

Email deliverability determines whether your messages land in the subscriber inbox or get diverted to spam. MarbleSend features a built-in DNS inspection engine that runs four sequential, non-blocking queries directly against authoritative nameservers without third-party API dependencies.

1. SPF (Sender Policy Framework)

Queries root TXT records for v=spf1 syntax. Verifies authorized SMTP host mechanisms, flags DNS lookup limits (>10 lookups cause hardfail), and checks enforcement qualifiers (~all or -all).

2. DKIM (DomainKeys Identified Mail)

Inspects selector._domainkey.domain.com to validate RSA or Ed25519 public cryptographic keys. Ensures outbound email headers can be cryptographically verified against sender domains.

3. DMARC Policy & Alignment

Checks _dmarc.domain.com for policy tags (p=none, p=quarantine, or p=reject). In 2026, Gmail and Yahoo bulk sender requirements mandate active DMARC records with rua reporting.

4. BIMI Brand Avatar & VMC

Queries default._bimi.domain.com for SVG Tiny P.S. brand logo files and Verified Mark Certificates (VMC), enabling verified brand avatars directly in Gmail, Yahoo, and Apple Mail inboxes.

6.7 Dual-Mode AI Engine: BYOK Providers & MarbleSend AI Credits

MarbleSend provides a flexible dual-mode AI engine for copywriting, subject line generation, and spam score audits: (1) Bring Your Own Key (BYOK) for connecting directly to major LLM providers with zero markup, and (2) MarbleSend AI Credits for instant, pay-as-you-go access to curated, top-performing models without creating developer accounts or managing API keys.

BYOK Mode 1: BYOK (Bring Your Own Key)

Connect directly to OpenAI, Anthropic Claude, Google Gemini, DeepSeek, Moonshot, OpenRouter, or private Ollama instances. API keys are encrypted at rest using AES-256-GCM. Requests flow straight from your server to the provider's API with zero middleman fees or rate markups.

METERED Mode 2: MarbleSend AI Credits (Curated Models)

Pre-funded AI credits managed directly through MarbleSend. Features curated, high-efficiency models specially benchmarked for email marketing conversion and deliverability. The vendor server dynamically manages optimal model routing and upgrades, deducting 1 credit per generation atomically.

DYNAMIC Dynamic Edge Pricing & Instant Checkout

Official add-on pricing is retrieved dynamically from admin.marblesend.com. Purchase top-up credit packs via global and local payment gateways with instant real-time balance activation.

PRIVACY AES-256-GCM Key Vault & Strict Data Privacy

All BYOK credentials are encrypted at rest with hardware-accelerated AES-256-GCM using your installation's unique APP_KEY. No prompt contents, contact lists, or recipient emails are ever stored or logged by the license server.

6.7.1 Default Model Selection & Execution Routing

Defines how MarbleSend routes AI copywriting, subject suggestions, and deliverability reviews across the application. You can switch between curated vendor relays and direct custom BYOK providers.

MarbleSend AI (Managed Relay)

Zero-configuration access to top-performing curated models specially benchmarked for email conversion. No external developer accounts or API keys required.

BYOK (Bring Your Own Key)

Direct server-to-vendor communication with zero markup. Select any configured provider and specific model identifier as your default generation engine.

Unified Generation Pipeline

Templates Studio, AI Subject Score Inspector, and AI Assist automatically execute against the active default selection with graceful fallback on credit depletion.

System State Persistence

The active selection is persisted in cm_settings.ai_provider_default, ensuring synchronized execution across all admin sessions and worker tasks.

6.7.2 MarbleSend AI Credits & Managed Relay Hub

MarbleSend AI Credits provide an instant, pay-as-you-go infrastructure for email copywriting without managing third-party AI billing, quotas, or rate limits.

Free Starter Credits Allocation

Every activated license receives complimentary starter AI credits dynamically retrieved from the license server, allowing immediate evaluation of AI Assist features.

Atomic Ledger & Idempotency

Generations are debited at exactly 1 credit per request via cm_addon_ledger. Unique transaction UUIDs prevent duplicate deductions during network retries.

Dynamic Edge Pricing & Top-Up

Credit packs (e.g. ai_100) are loaded dynamically from the vendor edge catalog. Payments are securely processed by our payment provider.

Real-Time Balance & 1-Click Sync

Webhook events instantly credit your account upon checkout completion. A manual 1-click refresh button provides immediate synchronization with the server.

6.7.3 Configured Providers & BYOK Infrastructure

Connect and orchestrate multiple external LLM providers with encrypted key storage, dual API protocols, and instant connection diagnostics.

Multi-Vendor Registry

Maintain simultaneous connections to OpenAI, Anthropic Claude, Google Gemini, DeepSeek, Kimi/Moonshot, OpenRouter, or private Ollama instances.

Dual Protocols & Custom Base URLs

Supports standard OpenAI-compatible endpoints (/chat/completions) and Anthropic messages format (/v1/messages) with custom proxy Base URLs.

AES-256-GCM Cryptographic Vault

All BYOK API keys are encrypted at rest with hardware-accelerated AES-256-GCM. Keys are never exposed in HTML responses or system logs.

Live Pre-Flight Diagnostics

Test connection button sends a lightweight probe to verify credentials and report round-trip latency (ms) before deploying models into production.

How to Obtain API Keys Across Major Providers

MarbleSend connects directly to AI providers from your server via BYOK without third-party proxies. Below are the official developer portal links, generation steps, and model reference directories for each supported provider:

OpenAI GPT-4o / o-series
Format: openai
Base URL: https://api.openai.com/v1
Steps: Log in to platform.openai.com → open Dashboard → select "API keys" from the left sidebar → click "+ Create new secret key" → name it "MarbleSend" → copy your key (starts with sk-...). Check platform.openai.com/docs/models for the latest model IDs.
Anthropic Claude Claude 3.5 / 3.7 Sonnet
Format: anthropic
Base URL: https://api.anthropic.com/v1
Steps: Sign in to console.anthropic.com → navigate to Settings → "API Keys" → click "Create Key" → label it "MarbleSend" → copy your secret key (starts with sk-ant-...). View docs.anthropic.com/en/docs/about-claude/models for the latest Claude model IDs.
Google Gemini Gemini 2.0 / 2.5 Flash
Format: gemini
Base URL: https://generativelanguage.googleapis.com/v1beta
Steps: Sign in with your Google account at aistudio.google.com → click "Get API key" in the navigation → click "Create API key" (choose or create a Google Cloud project) → copy your key (starts with AIzaSy...). Explore ai.google.dev/gemini-api/docs/models/gemini for latest Gemini models.
DeepSeek DeepSeek-V3 / R1
Format: openai
Base URL: https://api.deepseek.com
Steps: Log in to platform.deepseek.com → select "API keys" in the sidebar menu → click "Create API key" → enter name "MarbleSend" → copy the key (starts with sk-...). Visit api-docs.deepseek.com for available model IDs (such as deepseek-chat and deepseek-reasoner).
Kimi (Moonshot AI) Kimi k1.5 / Moonshot v1
Format: openai
Base URL: https://api.moonshot.cn/v1
Steps: Log in to platform.moonshot.cn → open Console → select "API Key Management" (API Key 管理) → click "Create" (新建) → copy your secret token. Visit platform.moonshot.cn/docs for model IDs (such as kimi-k3 and moonshot-v1-128k).
OpenRouter Aggregator (200+ Models)
Format: openai
Base URL: https://openrouter.ai/api/v1
Steps: Log in to openrouter.ai → navigate to Account → "Keys" → click "Create Key" → set optional spending limits → copy your key (starts with sk-or-v1-...). Browse openrouter.ai/models to access 100+ frontier and open-source models with unified routing.
Ollama & Local / Groq Local LLM / Ultra-Fast LPU
Format: openai
Base URL: http://localhost:11434/v1 / https://api.groq.com/openai/v1
Steps:
Local, Self-Hosted & Alternative Providers (Ollama, Groq, Mistral, GLM)

For local zero-cost AI, install Ollama (ollama.com), run your chosen model (e.g. ollama run llama3.1), set Base URL to http://localhost:11434/v1, select OpenAI format, and check "No Auth". For ultra-fast cloud inference, register at console.groq.com to generate free Groq API keys.

Configuring AI Providers in Settings (/settings)

Once you have obtained your API key and chosen model ID, configure it in MarbleSend with these simple steps:

1 1. Open Settings ➔ AI Providers

From the main sidebar navigation, click Settings, then switch to the "AI Providers" tab (direct access at /settings).

2 2. Use Quick Add or "+ Add Provider"

Click one of the Quick Add preset buttons (OpenAI, DeepSeek, Gemini, Anthropic, OpenRouter, etc.) to auto-fill settings, or click "+ Add Provider" for custom setups.

3 3. Verify Protocol & Base URL

Select protocol format: "OpenAI format" (standard for OpenAI, DeepSeek, Gemini, Groq, Ollama) or "Anthropic format" (for Anthropic Claude). Confirm the pre-filled Base URL.

4 4. Paste API Key Securely

Paste your secret API key into the field. MarbleSend stores all keys strongly encrypted with AES-256-GCM via Crypto.php with zero plain-text database exposure.

5 5. Define Model ID & Test Connection

Enter your desired Model ID and context window limit, then click "Test Connection". MarbleSend performs a live ping and authentication handshake before saving.

6 6. Set Default Active Model

Under "Default AI Model" at the top of the tab, select your configured provider and model, then click "Save Default". It is now active across Templates Studio!

6.8 Ed25519 Cryptographic License Grant Token Engine

MarbleSend verifies product authenticity using Ed25519 asymmetric cryptography via PHP's native libsodium extension. A signed License Grant Token is issued upon initial activation and validated locally without constant connection demands.

Ed25519 Signature Verification

Validates the digital signature with sodium_crypto_sign_verify_detached(). Any manual tampering with database records immediately breaks the mathematical signature, ensuring absolute integrity.

Domain & Tier Binding

The signed token embeds the licensed domain hostname, license tier (Regular or Extended), and active entitlements. Multi-domain moves can be initiated directly from the settings interface.

7-Day Offline Grace Resilience

Temporary network dropouts or license server maintenance will never interrupt running email broadcasts or cron execution. MarbleSend provides a 7-day grace window before restricting dispatches.

Soft-Lock Policy

If a license expires, MarbleSend enforces a graceful soft-lock: existing subscriber data, campaigns, reports, and export tools remain 100% accessible, while launching new campaigns is paused.

Automatic update checks

Once a day the worker asks the license server whether a newer version exists. It sends your purchase code and the installed version, exactly like the manual check, and nothing else. Turn it off with “Release updates” in this tab: no check is made and no update banner is shown. Development installs (APP_ENV=local) never make this call. Nothing about your contacts or Campaigns is ever sent.

Changelog and update emails

“Changelog” (sidebar, above Documentation) lists every release with its upgrade guide. When a newer release exists you see a banner on the dashboard (a security update shows a red banner on every page and cannot be dismissed), a dot next to “Changelog”, and, if enabled, one email per version to the admin address through your default SMTP profile. Release notes are written in English.

Support Reduction Playbook

Frequently Asked Questions & Diagnostics

Immediate solutions for the most common shared hosting issues.

7.1 Dashboard & Operational Diagnostics

7.1.1 Why does the Dashboard show "Worker Not Detected" or Inactive status?

Root Cause:

The OS crontab is not firing, the PHP CLI path is wrong, or a stale worker lock is preventing execution.

Diagnostics & Mechanism:

The worker updates cm_settings.worker_last_run with a UTC timestamp on every tick. The dashboard flags inactive if now - worker_last_run > 180 seconds.

Actionable Playbook & Fix:

Go to Settings → System. Copy the exact crontab line. If CLI crontab is restricted, configure the Web-Cron URL in cron-job.org every 1 minute. If lock is stuck, verify storage/worker.lock.

7.1.2 Why do email open and click rates differ between Dashboard KPI cards and external SMTP provider logs?

Root Cause:

Apple Mail Privacy Protection (MPP) pre-fetching, proxy caching, recipient image blocking, or adblockers stripping UTM parameters.

Diagnostics & Mechanism:

MarbleSend embeds a 1x1 transparent tracking GIF in outgoing emails to record cm_email_logs.opened_at. When recipients block remote images, opens are not recorded; Apple MPP pre-fetches images immediately upon arrival on Apple servers.

Actionable Playbook & Fix:

Treat Unique Clicks (CTR) as the primary indicator of engagement. MarbleSend routes all template links through public/click.php to record tamper-proof subscriber clicks.

7.2 Campaign Dispatch & Preflight Diagnostics

7.2.1 Why did my campaign pause or stop in the middle of sending?

Root Cause:

Shared hosting web server or PHP CLI execution limits (max_execution_time 30s/60s) reached during large broadcast dispatches.

Diagnostics & Mechanism:

MarbleSend processes the queue in atomic batches of 50 recipients. When time limits approach, the worker gracefully exits and commits cm_email_queue progress to avoid memory exhaustion.

Actionable Playbook & Fix:

No manual intervention required! The next 1-minute cron heartbeat automatically claims the next batch of pending items from cm_email_queue with zero duplicate emails sent.

7.2.2 Why does the 6-Point Automated Preflight Check block launching my campaign?

Root Cause:

Missing mandatory {{unsubscribe_link}}, invalid subject line, zero active recipients selected, or unclosed syntax tokens.

Diagnostics & Mechanism:

The Preflight Engine evaluates 6 critical compliance and deliverability vectors before allowing status transition to queued or sending.

Actionable Playbook & Fix:

Review the red warning badges in the campaign preflight modal. Insert {{unsubscribe_link}} (mandated by RFC 8058 and CAN-SPAM) and ensure selected tags match active contacts.

7.2.3 When does "Resend to Non-openers" become available, and how does it prevent subscriber fatigue?

Root Cause:

The action button in Campaign Report is disabled while the broadcast is still sending or before a safe cooldown period has elapsed.

Diagnostics & Mechanism:

MarbleSend requires campaign status = completed and recommends waiting at least 24 to 48 hours to allow subscribers across timezones sufficient time to open.

Actionable Playbook & Fix:

Once completed, click "Resend to Non-openers". This creates a new child campaign targeting solely contacts where opened_at IS NULL, allowing you to test an alternate subject line.

7.3 Contacts, CSV Import & Bounce Management

7.3.1 Why did the CSV Importer skip rows or fail to import certain subscriber records?

Root Cause:

Invalid RFC 5322 email syntax, duplicate email addresses with "Skip Duplicates" enabled, non-UTF-8 character encoding, or suppressed contacts.

Diagnostics & Mechanism:

MarbleSend inspects each row, validates syntax via filter_var(FILTER_VALIDATE_EMAIL), and checks against cm_contacts to prevent overwriting unsubscribed or bounced states.

Actionable Playbook & Fix:

Ensure your CSV file is saved as UTF-8 without BOM. Check column mapping (email, name, tags). Review the import progress modal for exact line numbers that were skipped.

7.3.2 How does MarbleSend handle Soft Bounces vs Hard Bounces?

Root Cause:

Permanent delivery failures (5xx SMTP error codes: mailbox does not exist) versus temporary delivery failures (4xx SMTP error codes: mailbox full, rate limited).

Diagnostics & Mechanism:

A 5xx response immediately escalates the contact to status = bounced. A 4xx response records a soft bounce attempt in cm_email_logs and retries up to 3 times before marking bounced.

Actionable Playbook & Fix:

Do not attempt to reactivate hard-bounced contacts. Doing so will severely penalize your domain reputation with major mailbox providers like Gmail and Yahoo.

7.4 Template Rendering, Outlook & Email-Safe Design

7.4.1 Why does my email template look broken or misaligned in Microsoft Outlook desktop?

Root Cause:

Microsoft Outlook for Windows uses the Microsoft Word (MSO) rendering engine, which does not support CSS Flexbox, Grid, margin auto, or HTML5 layout tags.

Diagnostics & Mechanism:

Word MSO only respects traditional table-based layouts (nested <table>, <tr>, <td>) with explicit pixel widths and inline styles.

Actionable Playbook & Fix:

Use MarbleSend's 8 built-in email-safe starter layouts. They use nested HTML tables and conditional MSO comments (<!--[if mso]>) to ensure bulletproof rendering across all Outlook versions.

7.4.2 Why does Gmail show "[Message clipped] View entire message" at the bottom of my emails?

Root Cause:

The raw HTML file size of the email exceeds Gmail's strict 102 KB (104,448 bytes) payload limit.

Diagnostics & Mechanism:

When the HTML payload exceeds 102 KB, Gmail truncates the message, cutting off content and hiding the footer unsubscribe link and tracking pixel.

Actionable Playbook & Fix:

Host images on external HTTPS servers instead of embedding inline base64 image strings. Avoid redundant CSS. In Templates Studio, use the "Clean HTML" formatter before launching.

7.4.3 Why is {{news_posts}} empty or missing in my email?

Root Cause:

The placeholder is only filled by a Newsletter schedule whose Content source is “News posts”. In a manual Campaign, an RSS schedule or a template-only schedule it is replaced with empty text.

Diagnostics & Mechanism:

The Campaign pre-flight check shows a warning when a template uses {{news_posts}} without a news post source; the preview in Templates → Posts always shows the filled block.

Actionable Playbook & Fix:

Send the template through a Newsletter schedule set to “News posts”, or remove the placeholder from templates used elsewhere.

7.5 Schedules, RSS Feeds & Worker Cadence

7.5.1 Why did my automated RSS schedule not send when new blog posts were published?

Root Cause:

The feed URL was unreachable, feed XML syntax was malformed, or the latest post GUID matched cm_schedules.last_rss_item_id.

Diagnostics & Mechanism:

MarbleSend stores a SHA-256 hash of the latest seen item in last_rss_item_id. If no new <item> or <entry> exists, the worker skips execution without creating queue entries.

Actionable Playbook & Fix:

Open the schedule in /schedules and click "Test Feed" to inspect the live XML response. Verify the RSS feed produces valid <pubDate> and unique <guid> tags. Ensure next_run_at is due.

7.5.2 What happens to recurring schedules if the server or worker cron was offline for several days?

Root Cause:

Multiple recurring occurrence intervals passed while the worker process was unable to run.

Diagnostics & Mechanism:

MarbleSend enforces a strict "Single Catch-Up Policy" in SchedulerService. Instead of queuing all accumulated missed runs (which would spam inboxes), it executes only the single most recent occurrence.

Actionable Playbook & Fix:

Once cron resumes, exactly 1 catch-up broadcast is compiled and dispatched. The schedule's next_run_at timestamp is automatically advanced to the next future scheduled date.

7.5.3 Why was my Ready post not sent by the News posts schedule?

Root Cause:

The post became Ready before the schedule's News posts mode was switched on, it was already delivered by this schedule, or the audience or template blocked the run.

Diagnostics & Mechanism:

Open the schedule history: “no new posts” means nothing is eligible, “no recipients” means the audience was empty, and “failed” means the template is missing or lacks {{news_posts}}.

Actionable Playbook & Fix:

Mark a copy of the post Ready after the schedule exists, fix the audience or template, then use Run now.

7.5.4 Can two schedules send the same post?

Root Cause:

Delivery is tracked per schedule, not per post.

Diagnostics & Mechanism:

Each News posts schedule keeps its own list of which posts it delivered, so the library shows the total “Sent N×”.

Actionable Playbook & Fix:

Yes — a post that is Ready after both schedules were created is sent once by each; neither schedule sends it twice.

7.6 Settings, Deliverability, Webhooks & Licensing

7.6.1 How do I diagnose SMTP error "535 Authentication Failed" or "550 Relay Access Denied"?

Root Cause:

Incorrect SMTP credentials, wrong port and encryption mismatch (Port 587 STARTTLS vs Port 465 SSL), or unverified sender email address.

Diagnostics & Mechanism:

Settings → SMTP includes a live socket test that displays the raw dialogue between PHPMailer and your SMTP server.

Actionable Playbook & Fix:

Use Port 587 with TLS for most relays. For Gmail/Google Workspace, generate an App Password. For Amazon SES, verify the sender domain. For cPanel, use the full email address as the username.

7.6.2 Why does the Deliverability Suite report SPF, DKIM, or DMARC validation errors?

Root Cause:

Missing DNS records on the sending domain, exceeding the 10-lookup limit in SPF, or selector prefix mismatch in DKIM.

Diagnostics & Mechanism:

MarbleSend performs sequential non-blocking DNS queries (dns_get_record) directly to authoritative nameservers for SPF (root TXT), DKIM (selector._domainkey), and DMARC (_dmarc).

Actionable Playbook & Fix:

Add your SMTP relay's include mechanism to your SPF record. Verify DKIM selector matches your relay provider recommendations. Ensure DMARC record contains a valid rua reporting address.

7.6.3 How does the Webhook Circuit Breaker work, and how do I reactivate a paused endpoint?

Root Cause:

Your destination endpoint returned repeated HTTP 5xx errors, connection timeouts (>10s), or 404 Not Found responses.

Diagnostics & Mechanism:

To prevent server queue congestion, MarbleSend tracks delivery attempts and automatically pauses the webhook (enabled = 0) after repeated consecutive failures.

Actionable Playbook & Fix:

Open the Webhook Delivery History drawer to inspect HTTP status codes and error payloads. Once your receiving server is fixed, click "Test Webhook", verify HMAC signature, and re-enable.

7.6.4 How does the Ed25519 License verification work, and what is the 7-Day Offline Grace window?

Root Cause:

Network connectivity dropouts, DNS resolution failures, or temporary downtime on the upstream licensing server.

Diagnostics & Mechanism:

License authenticity is verified locally in memory using libsodium sodium_crypto_sign_verify_detached(). Tampering with database records breaks the mathematical cryptographic signature.

Actionable Playbook & Fix:

MarbleSend provides a 7-day offline grace window (grace_until). Active broadcasts continue without disruption. If expired, existing subscriber data and reports remain 100% accessible under soft-lock.

WordPress plugin & REST API

WordPress & Third-Party Form Integrations

Collect subscribers on a WordPress site and send them to MarbleSend. The companion plugin has its own subscribe form and connects WPForms, Contact Form 7, Fluent Forms, Formidable Forms and Forminator.

Form Integration Inspector

Select a component to see its hook, what it sends and a tip for setting it up.

Interactive Inspector
REST API

REST API bridge

No shared database
WP-CRON

Delivery queue

Transient storage
NATIVE

MarbleSend Form

Shortcode, Block & Widget
WPFORMS

WPForms hook

wpforms_process_complete
CF7

Contact Form 7 hook

wpcf7_mail_sent
FLUENT

Fluent Forms hook

submission_inserted
FORMIDABLE

Formidable Forms

frm_after_create_entry
FORMINATOR

Forminator hook

submit_before_set_fields
AUTOMATION

Tag autoresponders

Tags and welcome emails

Database Impact:

Support Team Pro-Tip:

PREREQUISITE

Install the companion plugin first (marblesend-forms.zip)

MarbleSend runs on its own server, so WordPress needs a small plugin to send it subscribers. The plugin serves the built-in MarbleSend form and connects WPForms, Contact Form 7, Fluent Forms, Formidable Forms and Forminator. It's free, needs WordPress 6.3 or newer and PHP 7.4 or newer, and talks to MarbleSend only through the REST API.

Where do the Settings menus appear?

After you activate the plugin, WordPress shows two pages under Settings in wp-admin: MarbleSend Forms (connection URL, API key, default tags and the failure log) and MarbleSend Integrations (field mapping for each form plugin). Both live in WordPress. The MarbleSend dashboard doesn't have them. Your own forms, with their group, tags and spam protection, are managed under MarbleSend Forms in the sidebar.

1
1. Download marblesend-forms.zip

Download marblesend-forms.zip from Settings → License in MarbleSend, or directly from https://marblesend.com/download/marblesend-forms.zip. It is a ready-to-install WordPress plugin zip: upload it as it is, without unzipping it.

2
2. Install and activate in WordPress

In wp-admin go to Plugins → Add New → Upload Plugin, choose marblesend-forms.zip, click Install Now, then Activate Plugin. To update later, upload the newer zip the same way. WordPress offers to replace the installed version, and your settings stay.

3
3. Connect the API key (contacts:write)

In MarbleSend, open Settings → API & Webhooks and create an API key with the contacts:write scope. In WordPress, open Settings → MarbleSend Forms, enter your MarbleSend Site URL and the key, save, and click Test Connection. It should show Connected with the scope contacts:write. Then choose a Default Group and Default Tags from the lists loaded from MarbleSend, or create a new group there.

4
4. Map your forms

In wp-admin open Settings → MarbleSend Integrations. Every form plugin that is active appears with its forms. Tick Enabled, enter the email field and, if the form has one, the name field, add tags if you want, and save. The built-in form needs no mapping.

Without the plugin there is no MarbleSend Integrations page in wp-admin, and your WordPress forms can't send their submissions to MarbleSend.

8.1 How the plugin talks to MarbleSend

The plugin calls the MarbleSend REST API over HTTPS, so WordPress and MarbleSend can run on different servers. It has no access to the MarbleSend database.

No shared database

WordPress never reads MarbleSend's tables, and MarbleSend never reads wp_posts or wp_users. The two can run on separate hosts with separate databases.

Scoped API key

Every request carries a Bearer API key from Settings → API & Webhooks. The contacts:write scope is enough, so the key can't send campaigns or change schedules.

Connection test

Test Connection in Settings → MarbleSend Forms calls GET /api/verify-key and shows the scopes of the key, so a wrong URL or key shows up right away.

Upsert and tag merge

Each submission goes to POST /api/contacts. A new email becomes a contact. An existing one keeps its status and gets the new tags added to its current ones; its name changes only when the form sends one. With double opt-in on, a new contact stays pending until they confirm.

API Ingestion Endpoint (cURL Example)
curl -X POST https://your-domain.com/api/contacts \
  -H "Authorization: Bearer ms_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"email":"visitor@example.com","name":"John Doe","tags":["newsletter","source:wpforms"]}'

Optional: add "group_ids":[1,2] to put the contact in specific groups (list them with GET /api/groups; omitted = General). GET /api/contacts accepts group_id to filter by group.

8.2 Delivery queue and WP-Cron

A slow or unreachable MarbleSend shouldn't make a visitor's form hang or lose a lead. Submissions from form plugins are queued and sent in the background.

Nothing slow in the visitor's request

When a form plugin submits, MarbleSend_Integration_Base::dispatch() cleans the fields and stores them in a WordPress transient (set_transient). No request to MarbleSend happens while the visitor waits.

WP-Cron sends the queue

One WP-Cron event, marblesend_forms_process_queue, is scheduled for about a minute later and sends the queued submissions.

One attempt per queued item

WP-Cron sends each queued item once. The built-in form calls MarbleSend directly first and queues the submission for one WP-Cron attempt only if that call fails. A queued item is kept for up to one hour.

Failures are logged

If sending fails, or an item expires before WP-Cron could send it, the plugin adds a line to marblesend_forms_log (the last 50 records are kept) and shows an admin notice in wp-admin. Settings → MarbleSend Forms lists the log and the number of pending items.

Transient Queue Enqueuing Snippet
// Non-blocking submission buffer in WordPress
set_transient('marblesend_forms_queue_' . wp_generate_uuid4(), $payload, 3600);
if (!wp_next_scheduled('marblesend_forms_process_queue')) {
    wp_schedule_single_event(time() + 5, 'marblesend_forms_process_queue');
}

8.3 The built-in subscribe form

The plugin includes a subscribe form, so you don't need a form builder. Add it with a shortcode, a Gutenberg block or a widget. All three run the same code.

Shortcode, block or widget

Use [marblesend_form], the Gutenberg block marblesend/subscribe-form, or the MarbleSend Subscribe Form widget. The shortcode takes tags, show_name (1 or 0) and consent (the text of a required checkbox). The block and the widget offer the same options. Build your own forms under MarbleSend Forms → Add New Form and embed one with [marblesend_form id="12"].

Protection against bots

An off-screen honeypot field (marblesend_hp), a nonce requested when the form is submitted so a cached page can never serve an expired one, and a limit of 5 submissions per 60 seconds per IP (stored hashed). A request that fills the honeypot or lacks a valid nonce gets a normal-looking success message and is dropped. Too many requests get a short "please wait" message. Forms built in the editor can also use Cloudflare Turnstile or Google reCAPTCHA, set in Settings → MarbleSend Forms.

Sent as the visitor submits

The page doesn't reload. The plugin sends the contact to MarbleSend right away (5-second timeout). If MarbleSend can't be reached, or the plugin isn't configured yet, the submission goes to the queue for one WP-Cron attempt and the visitor still sees the success message. If the request itself fails, the form shows an error and can be sent again.

Names, tags and confirmation

When the visitor gives only an email, a new contact is named after the part before the @, because the API needs a name. An existing contact keeps its name. Every contact gets the tag source:wp-form plus the tags from the form and from Settings → MarbleSend Forms. With double opt-in on, the visitor sees a message asking them to check their email. The contact joins the group of the form, or the Default Group from Settings → MarbleSend Forms.

Shortcode & Gutenberg Block Embed Syntax

[marblesend_form tags="newsletter,inbound" show_name="1" consent="I agree to receive email updates and accept the privacy policy."]




8.4 WPForms (Lite & Pro)

WPForms is one of the most used form builders. The plugin reads the submission once WPForms has validated it, so you need no WPForms add-on.

Detection and hook

The plugin detects WPForms with function_exists('wpforms') and listens to the wpforms_process_complete action (priority 10), which receives the validated fields.

Field IDs

WPForms identifies fields by number. Select a field in the builder and read its ID at the top of the field options, for example Field ID #2. Enter that number in the mapping. A name field with first and last name is joined into one name.

Setup

1. Note the ID of the Email field and, if there is one, the Name field. 2. Open Settings → MarbleSend Integrations and find WPForms. 3. Tick Enabled, enter the IDs and any tags. 4. Save.

Source tag

Every contact from WPForms gets the tag source:wpforms next to your own tags, so you can send a campaign to just that group.

WPForms Complete Hook Architecture
add_action('wpforms_process_complete', function($fields, $entry, $form_data, $entry_id) {
    $email = $fields[1]['value'] ?? ''; // Numeric Field ID #1
    $name  = $fields[0]['value'] ?? ''; // Numeric Field ID #0
    MarbleSend_Integration_Base::dispatch('wpforms', $form_data['id'], $email, $name);
}, 10, 4);

8.5 Contact Form 7 (CF7)

Contact Form 7 defines its fields as tags in a text template. The plugin hooks in after the form's email has been sent.

Detection and hook

The plugin detects CF7 with class_exists('WPCF7_ContactForm') and listens to wpcf7_mail_sent (priority 10). A lead is forwarded only after the form passed the spam and validation checks and Contact Form 7 sent its own email. If email sending fails on your hosting, nothing is forwarded.

Tag names

CF7 names a field in its form tag, for example [email* your-email] and [text your-name]. Enter the name (your-email, your-name) in the Email Field and Name Field boxes.

Reading the submission

The plugin reads the posted data with WPCF7_Submission::get_instance()->get_posted_data(), which works with CF7's AJAX submit.

Setup

1. In Contact → Contact Forms open the form and note the tag names. 2. In Settings → MarbleSend Integrations find Contact Form 7. 3. Tick Enabled, enter your-email and, if the form has one, your-name, and add any tags. 4. Save.

Contact Form 7 Mail Sent Hook
add_action('wpcf7_mail_sent', function($contact_form) {
    $submission = WPCF7_Submission::get_instance();
    $data = $submission ? $submission->get_posted_data() : [];
    $email = $data['your-email'] ?? '';
    $name  = $data['your-name'] ?? '';
    MarbleSend_Integration_Base::dispatch('cf7', $contact_form->id(), $email, $name);
}, 10, 1);

8.6 Fluent Forms (Free & Pro)

The plugin listens to the action Fluent Forms fires after it saves an entry.

Detection and hook

The plugin detects Fluent Forms with function_exists('wpFluent') and listens to fluentform/submission_inserted (priority 10), which receives $entry_id, $form_data and $form.

Name Attribute

Fluent Forms identifies fields by their Name Attribute, for example email for Email and names for the Name field (first and last name are joined). Find it in the field settings under Advanced Options.

Form list

The Integrations page lists your forms by reading Fluent Forms' own form table, so you don't have to look up form IDs.

Setup

1. Check the Name Attribute of the email and name fields. 2. In Settings → MarbleSend Integrations, tick Enabled for the form and enter both. 3. Add tags and save. New contacts get the tag source:fluentforms.

Fluent Forms Insertion Action
add_action('fluentform/submission_inserted', function($entryId, $formData, $form) {
    $email = $formData['email'] ?? '';
    $name  = is_array($formData['names'] ?? null) ? implode(' ', $formData['names']) : ($formData['first_name'] ?? '');
    MarbleSend_Integration_Base::dispatch('fluentforms', $form->id, $email, $name);
}, 10, 3);

8.7 Formidable Forms (Lite & Pro)

The plugin reads the entry right after Formidable Forms saves it.

Detection and hook

The plugin detects Formidable with class_exists('FrmForm') and listens to frm_after_create_entry (priority 30), which receives $entry_id and $form_id.

Field ID

Formidable assigns a numeric Field ID (e.g. 12) to every field. Enter it into the mapping; field keys are not supported. Data is extracted via FrmEntry::getOne($entry_id)->metas.

Saved entries only

The plugin runs after the entry is created, so only submissions that passed Formidable's validation are forwarded.

Step-by-Step Setup

1. In Formidable builder, click the email field and note its Field ID. 2. In Settings → MarbleSend Integrations, enable form and enter the Field ID. 3. Save mapping. Ingested leads receive tag source:formidable.

Formidable Metas Extraction Hook
add_action('frm_after_create_entry', function($entryId, $formId) {
    $entry = FrmEntry::getOne($entryId, true);
    $email = $entry->metas[12] ?? $entry->metas['email-address'] ?? '';
    $name  = $entry->metas[11] ?? $entry->metas['full-name'] ?? '';
    MarbleSend_Integration_Base::dispatch('formidable', $formId, $email, $name);
}, 30, 2);

8.8 Forminator (Free & Pro)

The plugin listens to the hook Forminator fires while it processes a submission.

Detection and hook

The plugin detects Forminator with class_exists('Forminator_API') and listens to forminator_custom_form_submit_before_set_fields (priority 10), which receives $entry, $form_id and $field_data.

Element ID

Forminator gives every field an element ID such as email-1, name-1 or text-1. Enter these IDs in the mapping. The form editor shows them.

Form list

The Integrations page lists your Forminator forms by name, read with Forminator_API::get_forms().

Setup

1. Open Forminator and note the element IDs (email-1, name-1). 2. In Settings → MarbleSend Integrations, find Forminator and enter the IDs. 3. Add tags and save. 4. Send a test submission and check that the contact has the tag source:forminator.

Forminator Submission Pre-Field Hook
add_action('forminator_custom_form_submit_before_set_fields', function($formId, $response, $fieldData) {
    $fields = array_column($fieldData, 'value', 'name');
    $email  = $fields['email-1'] ?? '';
    $name   = $fields['name-1'] ?? '';
    MarbleSend_Integration_Base::dispatch('forminator', $formId, $email, $name);
}, 10, 3);

8.9 Tags, segments and autoresponders

Contacts from the WordPress plugin join the group chosen in Settings → MarbleSend Forms (Default Group) or on the form, and land in General when none is chosen. Use tags to segment them, or move them into groups from Contacts.

Capturing an email is the first step. Tags decide what happens to the contact next.

Source tags

Besides your own tags, every contact gets source:<slug>: source:wp-form, source:wpforms, source:cf7, source:fluentforms, source:formidable or source:forminator. Use them to send a campaign to the subscribers of one form plugin.

Welcome email

In /contacts → Autoresponders, create a rule for the tag you assigned to your WordPress form. When the contact becomes active (right after submitting, or after confirming the double opt-in), MarbleSend queues your welcome email with the template and SMTP profile you chose.

No automation service in between

The plugin sends leads straight to your own MarbleSend installation. A service such as Zapier or Make isn't part of the path.

Double opt-in and consent record

With double opt-in on, new contacts are stored as pending with consent_source = 'api'. MarbleSend emails a confirmation link ({{confirm_link}}). When the contact clicks it, the status changes to active and the time and IP address are recorded for GDPR.

Autoresponder Tag Structure (JSON Payload)
{
  "email": "lead@agency.com",
  "name": "Jane Smith",
  "tags": ["source:wpforms", "ebook-download", "vip-inbound"],
  "consent_source": "api"
}

8.10 Troubleshooting

Common problems when connecting WordPress forms, with the cause, how to check it and how to fix it.

Why do submissions show up late, or not at all, in MarbleSend?

Root Cause:

WP-Cron runs only when someone visits the site. On a quiet or staging site, queued submissions wait for the next visit. A queued submission is kept for one hour. If WP-Cron doesn't run in that time, the submission is lost and marblesend_forms_log records that one expired (it can't name the visitor, because the stored data has expired too).

Diagnostics & Mechanism:

Open Settings → MarbleSend Forms in wp-admin. The pending count shows items waiting for marblesend_forms_process_queue. A log entry saying a queued submission expired means WP-Cron didn't run for over an hour.

Actionable Playbook & Fix:

Open https://your-site.com/wp-cron.php?doing_wp_cron to run it once, or set up a real server cron job (* * * * * wget -q -O - https://your-site.com/wp-cron.php >/dev/null 2>&1).

Why does the log say "Skipped: no valid email in form submission"?

Root Cause:

The email field in the mapping has a typo, or the visitor sent an empty or invalid address that sanitize_email() rejected.

Diagnostics & Mechanism:

MarbleSend_Integration_Base::log_skip() writes an entry to marblesend_forms_log whenever an enabled form produces no valid email.

Actionable Playbook & Fix:

Check the identifier your form builder uses: a number for WPForms (for example 2) and Formidable Forms, the tag name for Contact Form 7 (your-email), the Name Attribute for Fluent Forms (email) and the element ID for Forminator (email-1).

Why does WordPress report an invalid API key or a missing scope?

Root Cause:

The key in WordPress was revoked, expired, deleted in MarbleSend, or created without the contacts:write scope.

Diagnostics & Mechanism:

Test Connection calls GET /api/verify-key. An HTTP 401 or 403 answer means authentication failed.

Actionable Playbook & Fix:

In MarbleSend open Settings → API & Webhooks and create a new key with the scope contacts:write (or admin). Copy the token that starts with ms_ into Settings → MarbleSend Forms in WordPress.

Why do Contact Form 7 leads go missing when the form shows an error?

Root Cause:

The plugin forwards a lead only after Contact Form 7 has sent its own email. When the email fails, CF7 shows its error message and the lead isn't forwarded.

Diagnostics & Mechanism:

Submit the form yourself. If you see "There was an error trying to send your message", CF7 couldn't send its email.

Actionable Playbook & Fix:

Fix email sending on your WordPress site, for example with an SMTP plugin. Once CF7 can send its email, the lead reaches MarbleSend.

Why is a contact named after its email address?

Root Cause:

The form has no name field, or no name field is mapped. MarbleSend's API requires a name, so a new contact gets the part of the email before the @.

Diagnostics & Mechanism:

Look at the form's row in Settings → MarbleSend Integrations: the Name Field box is empty. A contact that already existed isn't affected, because it keeps the name it has.

Actionable Playbook & Fix:

Add a name field to the form and enter it in the mapping. Contacts that already exist keep their current name; change it in /contacts if you need to.