=== WORPRO Newsletter ===
Contributors: worpro
Tags: newsletter, email marketing, automation, subscribers, mailing list
Requires at least: 6.4
Tested up to: 7.1
Requires PHP: 8.1
Stable tag: 0.2.47
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Newsletter system for WordPress that ties subscriber management, campaigns, and automations directly to your publishing workflow.

== Description ==

**WOR-PRO newsletter** is a self-hosted newsletter system for WordPress operators who want subscriber management, campaigns, and automations to live next to their content — not behind a separate SaaS subscription. Everything (lists, campaigns, automations, stats, tracking) runs inside your site's own database, and the free plugin never sends your data to any external service.

= Free features =

* **Subscribers & lists** — double opt-in, CSV import, a Gutenberg subscribe-form block, and WP Privacy Tools integration.
* **Campaigns** — send immediately, schedule for later, or auto-send when a post is published. Eight built-in code template presets plus your own custom templates, with test sends.
* **Automations** — five ready-made automations (Welcome, Welcome follow-up, Broadcast, Weekly digest, Reactivate) and three trigger types (subscriber confirmed, post published, schedule).
* **Segments** — build reusable audience segments (by status, source, locale, list membership, engagement score, or open/click dates) and narrow any campaign or automation to exactly that slice.
* **A/B testing** — split-test subject lines or bodies with deterministic variant assignment and winner selection.
* **Tracking & stats** — open/click tracking, per-campaign stats plus site-wide KPIs, 30-day SVG charts, and daily aggregation.
* **Sending engine** — an Action Scheduler-based batch queue with an hourly cap, up to three retries, and whitelist token replacement.
* **Multilingual** — compatible with Polylang and WPML, matching a subscriber's locale to a campaign's language automatically.
* **Accessibility** — WCAG 2.1 AA (focus-visible styles, screen-reader text, keyboard navigation).

= Everything is included =

Every feature in this plugin is free and fully functional — no license key, trial period, usage limit, or locked code. Segments, A/B testing, automations, and everything else work out of the box. The plugin transmits no data to any external service.

= Design principles =

* **No deployment to your production site from us** — the plugin updates through WordPress.org, and sends no data to any external service.
* **Respect shared libraries** — Action Scheduler is not prefixed, so the plugin coexists cleanly with others that bundle it.
* **Verification first** — every feature is verified with real end-to-end calls, not declared "done" from mocks alone.
* **Designed with a security mindset** — capability checks, nonces, output escaping, and queue-lock concurrency are handled explicitly throughout.

== Installation ==

1. Go to Plugins → Add New, search for "WOR-PRO newsletter", then install and activate. Alternatively, download the zip and use Plugins → Add New → Upload Plugin.
2. On activation, the plugin creates its database tables and a default list ("General") automatically.
3. Open the "WOR-PRO newsletter" menu on the left and follow the Dashboard guide to send your first campaign.
4. Explore Segments and A/B testing from the campaign editor — both are included and need no license.

= System requirements =

* WordPress 6.4 or higher
* PHP 8.1 or higher (8.2 recommended)
* MySQL 5.7+ or MariaDB 10.3+
* A working mailer — the site's `wp_mail()` must work; a dedicated SMTP plugin such as WP Mail SMTP is recommended.

== Frequently Asked Questions ==

= How much can I do for free? =

Subscriber management, campaign sending, the five automations, stats, tracking, the eight template presets, multilingual support, and accessibility — all of the core launch features are included in the free plugin. There are no limits on the number of sends or subscribers (only your SMTP capacity applies).

= Is anything locked, gated, or paywalled? =

No. Every feature — including segments and A/B testing — is fully functional in this plugin, with no license key, trial period, or usage limit.

= Can I run it alongside MailPoet, FluentCRM, or similar plugins? =

It is not recommended. Two newsletter plugins sharing the same Action Scheduler queue and automation cron on one site can conflict. You can migrate safely using CSV import.

= My emails are not being delivered =

This plugin uses your site's `wp_mail()`. First confirm that an SMTP plugin such as WP Mail SMTP or FluentSMTP is working. You can inspect the last_error message on failed/skipped rows in WOR-PRO newsletter → Tools → queue monitor.

= Can I send to more than 10,000 subscribers? =

Technically yes. The queue uses chunked inserts (500 rows per chunk) and batch processing (50 rows per batch) to stay memory-safe. Actual sending speed is determined by your site's SMTP throughput (its hourly limit).

= How does it handle GDPR? =

It integrates with WP Privacy Tools (Tools → Export Personal Data / Erase Personal Data), so this plugin's data is included when you export or erase a subscriber's email.

= Where is my data stored? =

Entirely in your site's MySQL database. The free plugin does not send your data to any external service.

== Screenshots ==

1. Dashboard — 30-day sends / open rate / click rate / unsubscribe KPIs
2. Campaigns — campaign list with status and send stats
3. Campaign editor — template selection, lists, and scheduling
4. Automations — the five built-in automations and creating a new one
5. Subscribers — subscriber list with filters
6. Stats — 30-day SVG chart

== Changelog ==


= 0.2.47 =
* Compatibility: verified against WordPress 7.1. No functional changes from 0.2.46.
= 0.2.46 =
* Fix: the bundled Action Scheduler library was never loaded, so background sending only worked on sites where another plugin (WooCommerce, WP Mail SMTP, WPForms) loaded it. On a site without one, campaigns fell back to sending inside the web request, and a list larger than the hourly batch limit could re-enter that fallback repeatedly until the request died. The library is now loaded by the plugin itself, so queued and scheduled sends run in the background on every site.

= 0.2.45 =
* Fix: unsubscribe and confirmation links failed when a mail client rewrote the `&` query separators as `&amp;` entities, which is common. PHP then read `amp;token` instead of `token`, the endpoint bailed out, and WordPress served the site's front page instead — the link appeared to do nothing. The endpoint now repairs the mangled query string, so links in already-delivered emails work again. When the token is genuinely missing it answers with the "link invalid" page (410) rather than falling through to the front page.
* Fix: someone who had confirmed a subscription and later unsubscribed could never sign up again. Confirming clears the token so the link cannot be replayed, but the re-signup path mailed the opt-in without issuing a new one, so the confirmation link arrived with an empty token and answered "invalid". A fresh token is now minted before that email goes out; the person stays unsubscribed until they confirm.
* Fix: the WORPRO_NL_VERSION constant lagged one release behind the plugin header, so asset cache-busting used a stale version string. Both now match.

= 0.2.44 =
* Fix: exception messages raised by the automation test runner were not escaped, and the "subscribers in no group" notice passed an uncast value to its message. Both are now escaped at the point they are produced.

= 0.2.43 =
* Add: subscriber CSV export (Tools → Export subscribers), filterable by status and list. The columns match what the importer reads, so an exported file can be edited and imported straight back — which also makes it the backup to take before a bulk change. Names beginning with =, +, -, or @ are escaped so spreadsheets cannot execute them.

= 0.2.42 =
* Fix: a removed subscriber's address could never be registered again — the admin form answered "a subscriber with this email already exists" and a CSV re-import skipped every one of them, because removal is a soft delete and the duplicate check still saw the removed rows. Re-registering now revives the removed record (its unsubscribe links keep working), while genuine duplicates are still skipped.
* Fix: someone who had been removed could re-subscribe through the signup form and silently vanish — the signup attached to the removed record, so the person never appeared in the list and never received a campaign.
* Fix: editing a scheduled campaign (changing its send time, for instance) replaced the lead article with the most recently published post. A campaign created from a post now keeps that post at the top when it is re-rendered.
* Fix: privacy export/erase requests now also cover removed subscribers, whose personal data was previously left in the table.

= 0.2.41 =
* Fix: the post editor's newsletter panel counted a campaign that is still waiting for its scheduled time as already sent ("Sent as a newsletter 5 times"). Pending and sent are now reported separately, so a scheduled send can actually be verified from the post.
* Add: a scheduled entry in that panel now links straight to the campaign screen, where it can be paused, rescheduled or sent immediately — the confirmation dialog promised this was possible but offered no way to reach it.

= 0.2.40 =
* Fix: scheduled automations now run on their own 5-minute cron event instead of riding the hourly statistics cron. A send configured for 10:00 previously left whenever that hourly cron happened to run (often around :50) — it now leaves within 5 minutes of the configured time.
* Fix: a scheduled occurrence is no longer lost when cron runs late. The trigger now checks whether the most recent scheduled occurrence has been run, so a delayed cron catches up the latest missed send (one send only, never a backlog). Occurrences older than the catch-up window (12 hours for daily, 24 hours for weekly/monthly) are skipped, and the reason is shown on the automation screen.
* Change: the duplicate-send guard is now per occurrence instead of per calendar day, which was inaccurate for weekly and monthly schedules.

= 0.2.39 =
* Fix: the "WORPRO Newsletter" panel on the post editor did nothing when clicked. Inside the block editor its form was dropped by the browser (a form cannot nest inside another), which also left the schedule and test-recipient fields stuck hidden. The panel now submits from the sidebar on either editor.
* New: set the email subject right in the post panel. Leave it blank for the post title, or write something like "[Weekly Brief] {{post.title}}".
* New: {{post.title}}, {{post.url}}, {{post.excerpt}} and {{post.date}} are replaced when a campaign is built from a post — in real sends and test sends alike. Test emails previously showed the raw token.
* Fix: scheduled automations ran on UTC hours. The hour you pick is now your site's local time, and the automation screen states the timezone (and the UTC equivalent) instead of a misleading offset hint.
* Fix: scheduled sends ignored the template chosen in Settings and always fell back to Curated Digest — for both test and real sends. The Settings default is now honoured, and the picker names it.
* New: a scheduled automation can send one chosen post ("main post") instead of a digest, so a recurring send produces the same email a post-publish send would.
* Change: a scheduled digest now collects posts published since its previous run and skips the cycle when there are none, instead of re-mailing the same old posts. Filter `worpro_nl/digest/require_new_posts` restores the old behaviour.
* New: the automation screen shows the exact subject a blank field falls back to, and `worpro_nl/digest/default_subject` can change it.

= 0.2.38 =
* Fix: choosing a subscriber group on a "Post published" automation silently stopped it from ever firing. The group picker is a condition for subscriber triggers and the audience for scheduled sends, but post-publish automations take their recipients from the action — so the setting is now ignored there (existing automations start working again without being re-saved) and is no longer offered for that trigger.
* New: test an automation before trusting it. The edit screen can now check the conditions against a sample post or group without sending anything, and send a preview to email addresses you type in (up to 10). Test sends never reach real subscribers and never create a campaign, so a later genuine publish still sends normally.
* New: automations now say why nothing was sent. The list shows the last result for each automation — sent, or skipped with the reason (condition that failed, or a post that was already sent).
* Fix: an automation that skipped a duplicate post send no longer counts that as a run.

= 0.2.37 =
* Security: HTML email templates now escape their output at the point of display with wp_kses() (post-context allow-list plus the cid: protocol for inline images), replacing manual suppression annotations. Rendering is unchanged; inline images, responsive styles, and Outlook fallbacks are preserved.
* Fix: sending a post as a newsletter to a list with no active subscribers now shows a clear "not sent — no active subscribers" warning instead of a misleading success message.

= 0.2.36 =
* Hardening: form input for segments (name, slug, match type) and the campaign exclude list are now sanitized on input before any processing or temporary storage.

= 0.2.35 =
* Reliability: a daily cleanup now prunes old tracking events, long-sent queue rows, and logs per the configured retention windows (tables no longer grow without bound); a watchdog re-queues any send left stuck by a crashed worker.
* Stats accuracy: opens and clicks from link scanners and prefetch bots are no longer counted (filter: worpro_nl/tracking/bot_user_agents).
* Campaigns: scheduled campaigns can be paused and resumed from the list; you can exclude specific subscribers from a send.

= 0.2.34 =
* Cleanup for WordPress.org review: removed a leftover inline onclick (now a delegated confirm handler) and a vestigial optional dependency on the A/B splitter; trimmed changelog wording.

= 0.2.33 =
* Sending: fixed a race where two overlapping send workers could mail the same recipient twice; throttled batches no longer burn a recipient's retry attempts; a batch error message no longer bleeds onto later rows; a campaign whose every recipient failed now finalizes as Failed (retryable) instead of Sent.
* A/B testing: opens and clicks are now attributed to the right variant, so winner selection reflects real results; choosing a winner and rebuilding now sends the winner to everyone already queued.
* Automations: scheduled automations (Welcome follow-up, Weekly digest, Reactivate) now actually run and build a digest from recent posts; schedule frequency/day/hour is saved; only the due automation fires (no more non-due or double sends); broadcasts no longer duplicate when a post is re-published; content-length and side-list options are applied.
* Duplicating a campaign now copies its template, body, images, and settings.
* Tracking: click links send no-cache headers (accurate click counts); opens/clicks are only recorded for real recipients.
* Privacy: erasing a subscriber now also removes their tracked open/click history, and the data export includes it.
* Lists: the subscriber count now reflects who will actually receive (excludes globally unsubscribed/removed members).
* Deleting a segment in use by a campaign is blocked; a broken segment reference falls back to the list audience instead of silently sending to nobody.
* Email templates: all footer/label text (including "Unsubscribe") is translatable and the document language follows the site locale.
* Import: an unrecognized status column value keeps the default (subscribed) instead of silently marking the row pending.
* Deleting a campaign now cleans up its queue, events, and stats rows.
* Various admin fixes (bulk-action reliability, notice display, invalid reply-to feedback, REST campaign fields).

= 0.2.32 =
* All email templates hardened for reliable cross-client rendering: Outlook (Word engine) now centers the layout and holds the intended width, with mobile stacking and image resets applied consistently. Design is unchanged on modern clients.

= 0.2.31 =
* Fixed: picking a card/digest template (Bento grid, Editorial magazine, etc.) for a campaign now actually sends in that template — it silently fell back to the Simple layout before.
* Fixed: SVG site logos/favicons are skipped in emails (they don't render in most mail clients) and fall back to the site-name wordmark, and inline images no longer break on the second email rendered in one run.
* Fixed: Bento grid cards no longer show a stray gray line on the left, and the header aligns with the cards.
* Fixed: bulk actions (delete, unsubscribe) on the Subscribers, Lists, and Campaigns screens now work — they were silently ignored.
* Fixed: blog-post automations now respect the selected segment (it was dropped, sending to the whole list).
* Blog newsletters skip the default "Uncategorized" category label when the post has a real category.
* Sent campaigns show a "View" link (not "Edit") and offer to duplicate for editing, since a sent campaign is read-only.

= 0.2.30 =
* Segments and A/B testing are now included free for everyone — no license, gating, or separate add-on required.
* Segments now *filter* (narrow) a campaign or automation audience to subscribers who also match the segment, instead of widening it.
* Post-publish auto-send and campaigns can now target a specific list and segment.
* Fixed: broken logo/images in email templates; campaign edits not saving (subject, body, list, template); blog-published newsletters showing the wrong category; bulk delete/unsubscribe on the subscribers, lists, and campaigns screens.

= 0.2.29 =
* Internationalization: the built-in email template names and descriptions now use English source strings (with a bundled Korean translation) instead of Korean source strings, so they display correctly in every locale.

= 0.2.28 =
* Removed the automation wait-step and webhook actions; the free build ships no locked or gated code.
* Tightened REST permissions: subscriber, list, and campaign read endpoints now require the matching management capability.
* Fixed plugin translation path resolution to not assume a fixed plugin folder name.
* Replaced inline onclick handlers with delegated admin scripts; copy-to-clipboard now uses the Clipboard API.
* Updated bundled Action Scheduler to 4.0.0.
* Korean translation: corrected the subscriber "Subscribed" status label and unified terminology across the admin.


= 0.2.25 (2026-06-16) =

* i18n: translated 48 more recently-added strings (reply policy, subscribe form color/layout/radius, send-only inquiry link, sender warnings) that were missing from the catalog — the Settings page is now fully Korean under ko_KR.

= 0.2.24 (2026-06-16) =

* Fixed: "Auto" default template fell back to the first magazine template instead of Simple (the post base template was removed in the consolidation). Auto now correctly resolves to Simple.
* Improved: template preview now picks content-rich published posts so real summaries/excerpts show, instead of grabbing a thin one-line note.
* i18n: completed Korean (ko_KR) translation across the admin (Settings, sender, email design, lists, segments, campaigns, automations) — 486 previously-untranslated strings.

= 0.2.23 (2026-06-16) =

* Performance: inlined email images (logo, thumbnails, spacer) are now cached for a day, so template preview and sends no longer re-download them every time — the preview opens fast instead of lagging.

= 0.2.22 (2026-06-16) =

* Bento Grid equal height (final): a transparent spacer image forces each card to a fixed height in every client (image height is universally honored), with title/excerpt clamped so content never overflows.

= 0.2.21 (2026-06-16) =

* Bento Grid cards now have a hard fixed height (HTML height attribute + CSS), so every card is identical height in all clients including Gmail.

= 0.2.20 (2026-06-16) =

* Korean subject particle (이/가) after the site name is now chosen by the final consonant, so it reads correctly for any site name. Bento Grid card titles are length-clamped so the grid stays even.

= 0.2.19 (2026-06-16) =

* Bento Grid equal-height fix (take 2): cards now use the table-row equal-height technique with height:100% inner tables, so a short title no longer makes a shorter card. Works in Gmail.

= 0.2.18 (2026-06-16) =

* Curated Digest issue number is now a real sequential counter (Issue #1, #2, ...) bumped on each send, replacing the arbitrary post-ID value.

= 0.2.17 (2026-06-16) =

* Bento Grid: cards are now a fixed height with the read link pinned to the same bottom-left position, regardless of title/excerpt length.

= 0.2.16 (2026-06-16) =

* Email header logo: SVG site logos (unsupported by email clients) now fall back to the raster site icon, so the logo no longer shows broken.
* Simple template typography refined (heading, accent rule, body spacing).

= 0.2.15 (2026-06-16) =

* Minimal Mono: in full body mode the lead note now shows the complete article (detail + list), matching the "full detail + list" content mode.

= 0.2.14 (2026-06-16) =

* Template lineup streamlined to 8: the 7 magazine presets + Simple. The older base presets (post/digest/newsletter/announcement/receipt/promotion/event), which repeated one frame and ignored body mode, are retired from the picker (classes kept for backward compatibility).
* Bento Grid cards are now equal height.
* Korean template names throughout.

= 0.2.13 (2026-06-16) =

* Restored the 7 magazine template presets (Editorial Magazine, Bento Grid, Minimal Mono, Personal Letter, Event Invitation, Curated Digest, Product Update) — 15 built-in templates total.

= 0.2.12 (2026-06-16) =

* Korean translations for newsletter template text (digest intro and CTA, event When/Where/RSVP, promotion Shop now/Learn more, announcement label) so template emails render fully in Korean on ko-locale sites instead of mixing English.

= 0.2.11 (2026-06-16) =

* Fix: images in the template preview (logo, post thumbnails) now render. Embedded (cid:) images were being blanked in the preview; they are now inlined as data: URIs so the preview shows the full design.

= 0.2.10 (2026-06-16) =

* Fix: the template preview now renders in the modal instead of showing raw HTML source (the preview was served under a JSON content-type; it now sends text/html correctly).
* Korean translations for the double opt-in confirmation email (subject, greeting, body, confirm button, footer note) so Korean-locale sites no longer mix English and Korean.

= 0.2.9 (2026-06-16) =

* Subscribe form corner radius — set the form's rounding under Settings → Email design (or per form with the shortcode attribute radius="N"). It applies to the email field and the button together, so you can match your theme's button shape (e.g. 30 for rounded buttons, 0 for square).

= 0.2.8 (2026-06-16) =

* Subscribe form option to hide field labels via the shortcode attribute show_labels="0" — for compact forms where a single field plus its placeholder is self-explanatory. The label stays available to screen readers (accessibility preserved); the consent label and button are unaffected.

= 0.2.7 (2026-06-16) =

* Subscribe form inline layout — email field and button on one row (button on the right), for full-width footer bars. Set it under Settings → Email design → Subscribe form layout, or per form with the shortcode attribute layout="inline".

= 0.2.6 (2026-06-16) =

* Korean translations for the public subscription form and confirmation pages — subscribe button, consent text, status messages, and the opt-in / unsubscribe result pages. English stays the default; Korean-locale sites now display localized text.

= 0.2.5 (2026-06-16) =


= 0.2.4 (2026-06-15) =

* Per-campaign reply policy: each campaign can now override the site-wide reply policy — "Use site default", "Replies allowed", or "No reply (send-only)" — in the campaign editor. The chosen policy drives both the Reply-to header and the footer notice at send time, and test sends reflect it.

= 0.2.3 (2026-06-15) =

* Fix: the no-reply footer notice is now resolved at send time against the live reply policy, exactly like the Reply-to header. Previously the notice was baked into a campaign's body when it was created, so changing the reply policy afterward could leave the footer and the Reply-to header disagreeing. Test sends and the Tools test email reflect the current policy too.

= 0.2.2 (2026-06-15) =

* Reply policy: choose "Replies allowed" or "No reply (send-only)" under Settings → Sender. Send-only drops the Reply-to header on bulk newsletters and adds a "do not reply — contact us here" notice to the email footer. Opt-in confirmation emails always stay reply-able.
* Inquiry link setting: point the footer "Contact us here" link at a URL or email address (falls back to the site admin email).
* Send-readiness now warns when an SMTP plugin (e.g. WP Mail SMTP with the Google/Gmail mailer) will rewrite the From address to the authenticated account, with guidance on send-as aliases or switching to a no-reply policy.

= 0.2.1 (2026-06-15) =

* Subscription form text color: a Settings → Design option (Form text color) for placing the subscribe form on dark backgrounds (e.g. a black footer). Inputs keep dark text for readability; blank inherits the surrounding color.

= 0.2.0 (2026-05-27) =

* A/B testing and Segments are fully included in the free plugin. The free plugin makes no external service calls on its own.
* i18n cleanup: 268 Korean message IDs converted to English message IDs, with the Korean translation preserved in ko_KR.po; locale handling works in both directions.
* Multilingual sending UX (Free): locale is captured automatically at signup (html lang plus a Polylang referer fallback), the campaign form has a Language selector, EmailShell renders in the campaign's language, and auto-campaigns capture the source post's Polylang language.
* Passes WP.org Plugin Check with zero errors.

= 0.1.0 (2026-05-22) =

* Initial release.
* All included, free: subscribers / lists / campaigns / segments / A/B testing / five automations / tracking / stats / eight templates / Polylang & WPML / accessibility.
* WCAG 2.1 AA compliant.

== Upgrade Notice ==

= 0.2.0 =
Adds multilingual sending UX.

= 0.1.0 =
Initial release.
