Lead Capture & Campaigns — full changelog

== Changelog ==
= 0.9.20 =
* Fixed: the background job schedule was never created on a fresh activation (the one-minute
  interval was not registered before scheduling). Activation now registers it first, and the hourly
  fallback re-creates a missing schedule on its own.
* Changed: every inline script and style is now an enqueued file, loaded only on the plugin's own
  screens.
* Changed: the free build contains no license checks and no Pro code. The Pro features (wake-up and
  re-engagement, scheduled and automated delivery, attachments, warm-up, the Pro templates,
  webhooks) exist only in Optcatch Pro, a separate plugin from optcatch.com.
* Fixed: unslashed input and a dead test-email handler flagged by Plugin Check; the REST tick token
  is now checked in the route's permission callback.
* Changed: internal names now use the "optcatch" prefix (WordPress.org requires prefixes of at least
  four characters). Settings, contacts, campaigns, tables and scheduled tasks move automatically on
  the first page load after the update; if they cannot be moved, the plugin stays inactive, shows the
  reason to administrators and retries on every page load.
* Breaking: the REST base path is now `/wp-json/optcatch/v1/` (or `/?rest_route=/optcatch/v1/...`):
  webhook endpoints `optcatch/v1/lead`, `optcatch/v1/webhook` and `optcatch/v1/capture`, heartbeat
  `optcatch/v1/tick`. Update the URL in Zapier, Make and other webhook senders and in any server
  crontab that calls the tick. The `lcc/v1` paths no longer exist. The `X-LCC-Key` and
  `X-LCC-Tick-Token` header names are still accepted next to `X-Optcatch-Key` / `X-Optcatch-Tick-Token`,
  and existing webhook keys stay valid.
* Breaking: the Gmail / Microsoft OAuth redirect URI is now
  `.../wp-admin/admin.php?page=optcatch-settings&action=optcatch_google_oauth_callback` (Google) and
  `...&action=optcatch_microsoft_oauth_callback` (Microsoft). Add the new redirect URI to your OAuth
  app in Google Cloud / Azure before you reconnect an account; already connected accounts keep working
  with their stored refresh tokens.
* Breaking: WP-CLI commands are now `wp optcatch run-jobs` and `wp optcatch wakeup`.
* Breaking: filters and actions use the `optcatch_` prefix, e.g. `optcatch_lead_form_selector`,
  `optcatch_consent_checkbox_selector`, `optcatch_capability` and `optcatch_upgrade_url`. Update
  custom code that hooks the old `lcc_` names.
* Breaking: the shortcode is now `[optcatch_form]`; the older `[lead_capture_form]` and three-letter
  shortcode names were removed. Replace them on pages that embed the form.
* Unsubscribe links in emails sent before this update keep working.
* Admin pages moved to `admin.php?page=optcatch-...`; old `page=lcc-...` bookmarks redirect.
* Purge your page caches after updating: cached pages still carry the old form field names and
  script data.

= 0.9.19 =
* Fixed: contacts captured from forms on your own site were tagged "webhook" even though they did
  not come from a webhook. They are now tagged "auto-capture"; only leads that arrive through the
  Pro webhook key (with no tags of their own) get the "webhook" tag. Existing contacts keep their
  tags; remove the old "webhook" tag from them in Contacts if you like.
= 0.9.18 =
* Changed: re-engagement emails now have their own section. The guide gets a dedicated "Step 3b:
  Re-engagement Emails" (what it does, why it pays off, recommended delays, how to activate) in
  both versions, with a link in the guide navigation; the Wake-Up page's Re-engagement card
  explains what it does and its benefits above the settings; the dashboard card and the readme
  describe it as the second touch for leads who never replied. Also fixed the follow-up card's
  status text, which described the re-engagement email by mistake.
= 0.9.17 =
* Changed: clearer help text. The campaign page now explains that delivery keeps running in the
  background without site visitors (heartbeat, local fallback, Settings → Background delivery);
  the sender guide says a @gmail.com / @outlook.com address is fine through that provider's own
  account and only a problem as the From address on custom SMTP or SendGrid; the Contact Form 7
  acceptance tip is marked optional (auto-detect already captures CF7 forms); the shortcode is
  described as the option for sites with no form at all; and the webhook FAQ says any application
  that can send an HTTP request can push leads in.
= 0.9.16 =
* Added (Pro): the Pro template library is now real. Six extra ready-made designs open in the
  Studio like any starter preset: event / webinar invitation, flash sale with coupon code,
  lead-magnet delivery, customer story, appointment confirmation & reminder, and a review
  request with one-click rating buttons. The free version keeps its six starter presets and
  unlimited templates of your own; the Templates page describes the Pro designs instead of
  showing locked cards. A campaign already built on a Pro design keeps sending if the license
  lapses.
= 0.9.15 =
* Changed (Pro): the webhook / REST API endpoint for external tools (Zapier, Make, Typeform,
  landing-page builders) is now a Pro feature. The free version keeps capturing every form on
  your own site exactly as before; the secret webhook key, the /lead and /webhook routes and
  the integration guide ship in Pro only. On a lapsed Pro license a connected tool gets a clear
  "requires an active Pro license" reply instead of a silent failure, and the key is kept so
  reactivating Pro restores the integration.
* Changed: Settings → Advanced no longer offers a custom "lead form class". Forms without a
  consent checkbox of their own are handled by Universal Auto-Detect; forms with one carry the
  consent-checkbox class (still configurable). The `lcc-lead-form` marker keeps working, and a
  lead-form class name you customised on an earlier version is still honoured.
= 0.9.14 =
* Changed (Pro): the immediate wake-up follow-up now needs an active Pro license at run time,
  like re-engagement already did. On a lapsed license your settings stay saved and editable,
  but no new follow-ups are scheduled or sent, the Wake-Up page shows why, and the dashboard
  marks the automation as "Needs Pro". Nothing changes for the free version.

= 0.9.13 =
* Fixed: the dashboard checklist, the setup guide and the campaign help text no longer describe
  warm-up or wake-up steps that are not part of this version.

= 0.9.12 =
* Changed: automated mailbox warm-up is now a Pro feature. Every sending account in the free
  version keeps its own daily sending limit, per-minute sending speed, automatic rotation and
  failure protection; Pro adds the schedule that raises that daily limit for you as the
  mailbox builds reputation.
* Changed: wake-up follow-up emails are now a Pro feature, together with the re-engagement
  email and wake-ups for imported contacts that were already Pro. The free version sends
  campaigns to a segment whenever you choose; Pro follows up each new contact by itself.
* Changed: in the campaign builder, scheduled delivery, automated sequences, attachments and
  hosted download links are now presented as proper feature cards explaining what each one
  does, instead of a one-line note.

= 0.9.11 =
* Changed: the plugin folder and text domain are now `optcatch-lead-capture-campaigns`, matching the
  WordPress.org slug (required for translations). Settings and data are untouched; if you installed
  an earlier zip by hand, deactivate the old folder and activate this one.

= 0.9.10 =
* Changed: the plugin is now called Optcatch – Lead Capture & Campaigns (same plugin, same settings and data).
* Changed: multiple sending accounts with automatic rotation are now part of the free version.
* Fixed: code annotations that the generated free build moved out of place, so WordPress.org's
  Plugin Check reported them; no behaviour change.

= 0.9.9 =
* Changed: the free version has no usage limits any more. Contacts, saved segments and custom
  templates are unlimited, and CSV import accepts up to 5,000 rows per file for everyone. Pro
  differs only by its extra features (multiple sending accounts with rotation, scheduled and
  automated sends, re-engagement emails, wake-ups for imported contacts, attachments and
  hosted download links, the full template library).
* Changed: the free version no longer shows disabled Pro controls; Pro features are described
  with an upgrade link instead.

= 0.9.8 =
* Fixed: a database query annotation was lost in the generated free build, so WordPress.org's
  Plugin Check reported it; the query itself is unchanged and safe.

= 0.9.7 =
* Added: for a campaign that takes an hour or more to send, the campaign builder now explains
  that it shares your sending limits with wake-up emails and newer campaigns, so you can choose
  the segment size and the time to start.
* Changed: a sending account with warm-up on can no longer be saved with a Day 1 Limit above
  its Daily Sending Limit (warm-up would have no effect); the form explains how to fix it.
* Fixed: the warm-up preview now shows the real daily limit when the Daily Sending Limit holds
  sending below the warm-up schedule.
* Fixed: when editing a campaign, the recipient count and delivery estimate now show the
  selected segment instead of the first one.

= 0.9.6 =
* Fixed: activating Lead Capture & Campaigns Pro while the free plugin was still active
  stopped with "Plugin could not be activated because it triggered a fatal error". Pro now
  takes over from the free plugin automatically, keeping all your data.

= 0.9.5 =
* Fixed: a send that finished just after midnight could reset the new day's sent counter and
  allow extra emails beyond the daily limit.
* Changed: the free plan now includes one custom email template of your own (the Email
  Studio, Duplicate and "Customize in Studio" were Pro-only). Editing and deleting that
  template always stays available; Pro is unlimited.
* Changed: the free allowances for saved segments (2) and custom templates (1) are counted
  site-wide, like the 500-contact limit, so the owner filter can no longer change them.
* Changed: SMTP warm-up now advances by usage, not by the calendar. The next day's higher
  limit unlocks only after at least 80% of the current limit was actually sent in a day
  (filter optcatch_warmup_advance_percent); an under-used day keeps the same limit.

= 0.9.4 =
* Fixed: after clicking "Allow & Continue" on the optional usage-data opt-in, the browser
  showed "Sorry, you are not allowed to access this page" while Freemius waited for the email
  confirmation. It now returns to the plugin Dashboard, which asks you to check your email.

= 0.9.3 =
* Changed: Pro features now ship only in the Pro download. Activating a license installs
  Pro in place of the free plugin and keeps your contacts, campaigns and settings.
* Changed: new installs default to Strict Consent — a visitor is stored as a subscribed
  contact only when they tick a consent checkbox. "Automatic Consent on Submission" is
  still available under Settings; sites that already saved a choice keep it.
* Fixed: contacts captured under "Automatic Consent on Submission" were stored with the
  marketing checkbox wording and version, as if the visitor had ticked a box they never
  saw. They are now recorded with the policy that applied (version `policy-submission`).
* Fixed: a later submission without a ticked checkbox no longer overwrites a contact's
  existing checkbox consent record.
* Fixed: the capture script no longer reports consent when no checkbox was ticked.
* Fixed: a ticked newsletter choice in a Gravity Forms checkbox group was missed unless it
  was the first choice; a ticked Fluent Forms checkbox is recorded as a checkbox consent.
* Fixed: WordPress comment forms were captured, and ticking core's "Save my name, email, and
  website in this browser" box was treated as marketing consent. Comment, login, registration
  and password-reset forms are now never captured (also enforced server-side).
* Changed: only a marketing opt-in checkbox (newsletter, marketing, subscribe, opt-in, or the
  configured consent class) counts as consent to marketing email. A privacy-policy or terms box
  no longer does, in the capture script and in the Contact Form 7, WPForms, Elementor, Gravity
  Forms, Fluent Forms and Ninja Forms integrations.
* Fixed: universal auto-detect no longer captures theme or plugin login forms (any form with a
  password field), and a Contact Form 7 acceptance box no longer counts as marketing consent
  just because it is optional.
* Fixed: the Free plan's 500-contact limit is now counted across the whole site and
  enforced on every way a contact is added (form capture, webhook/REST, CSV and manual
  import, admin), so a site can no longer go past 500 contacts.
* Fixed: importing a CSV close to the limit added fewer contacts than there was room for
  and blamed the 100-row file limit. It now adds exactly the remaining slots, still updates
  existing contacts, and says how many new contacts were left out because of the limit.
* Fixed: at the contact limit the [optcatch_form] shortcode no longer shows visitors
  "Failed to save subscription".
* Fixed: a Contact Form 7 submission could be stored twice at the same moment (by the
  capture script and by the Contact Form 7 integration), logging "Duplicate entry" database
  errors and dropping one side's tags. Both now merge into one contact with all tags. The
  same applies to WPForms, Elementor, Gravity Forms, Fluent Forms and Ninja Forms.
* Fixed: a submission handled by both capture paths no longer replaces the form's own
  consent checkbox wording with the plugin's default wording.
* Fixed: the Contact Form 7 integration no longer processes one submission twice.
* Fixed: the Free plan now sends from exactly one sender, as intended. A site with several
  enabled sending accounts on Free (for example after a Pro license lapses) no longer rotates
  across them. Settings shows which sender is used and lets you choose another; the other
  accounts stay saved and are used again when Pro is active.
* Fixed: on the Free plan (or after a Pro license lapses) creating a scheduled or automated
  campaign could send it immediately to the whole segment. A new scheduled or automated
  campaign is now refused with a notice and nothing is sent. An existing scheduled campaign
  keeps its date when edited (changing the date needs Pro) and is never sent early.
* Fixed: if a campaign could not be queued when saving an edit, its content was cleared.
* Fixed: an SMTP account with an empty username and password (a local mail server or a relay
  that allows your server's IP) failed every send with "Could not authenticate", because the
  plugin logged in as the From address anyway. It now sends without a login. An empty username
  with a password still logs in as the From address. A saved SMTP password can now be removed.
* New: Settings > General & Compliance > "Delete all plugin data when the plugin is deleted".
  Off by default: deleting the plugin now KEEPS your contacts, campaigns, templates, settings
  and license, so a reinstall restores everything (previously the tables were always dropped).
  On: deleting the plugin removes everything it stored, including saved SMTP passwords, API
  keys, cached OAuth tokens and the webhook secret, and disconnects this site's license
  (previously these settings were left behind even though the tables were dropped). Releasing
  the activation on the licensing server is best effort if the site is offline. On multisite,
  each site follows its own setting.
* Fixed: a Contact Form 7 form was sent two or three times for one click (the site owner
  received the same message repeatedly) whenever the plugin did not capture the visitor, for
  example when the optional consent box was left unticked.
* Fixed: the "Required Checkbox" consent setting is now enforced. Contact Form 7 rejects the
  submission with a message next to the box; other forms are blocked in the browser until the
  box is ticked. A box that is hidden or removed never blocks a form. The setting now explains
  that a required marketing checkbox belongs on newsletter sign-up forms only (GDPR).
* Fixed: until the optional usage-data opt-in was answered, every plugin page except the
  Dashboard (Settings, Contacts, User Guide, and the Settings link on the Plugins page) showed
  "Sorry, you are not allowed to access this page". All pages now work right away.
* Fixed: upgrade buttons never reached the pricing page (they looped back to Settings). They
  now open the pricing page, or the secure Freemius checkout before the opt-in is answered.
  The free version explains how to install Pro after purchase instead of showing a license-key
  button that could not accept a key.
* Fixed: a sending account's daily allowance is now spread across the day. After a daily
  limit reset (or a raised limit) a waiting campaign used to send the whole new allowance
  within minutes; now an account sends at most its daily limit ÷ 24 plus its per-batch
  number in any hour, and a campaign that is only waiting for the next hour stays "Sending"
  instead of being deferred to midnight.

= 0.9.2 =
* Fixed: opening an unsubscribe link no longer unsubscribes immediately. The link now
  shows a confirmation page with an "Unsubscribe" button, so email security scanners
  that pre-open links (Microsoft Safe Links, Mimecast, ...) can no longer unsubscribe
  real recipients by accident.
* Added: RFC 8058 one-click unsubscribe (List-Unsubscribe-Post header) on campaign and
  wake-up emails, as Gmail and Yahoo expect from bulk senders. The mail client's own
  "Unsubscribe" button works without opening a page.
* Fixed: the email's HTML title showed the raw subject with merge tags such as
  {{first_name}}; it now shows the same personalized subject the recipient sees.
* Fixed: names with an apostrophe or ampersand appeared HTML-encoded in email subjects
  (O&#039;Brien instead of O'Brien).

= 0.9.1 =
* Listing/metadata release, no functional change. Tested with WordPress 7.1.
* Plugin author and contributor are now listed personally (Elias Toss / eliastoss);
  Plugin URI and Author URI point to the WordPress.org plugin page and profile.
* readme: clearer description, FAQ and screenshots for the WordPress.org directory.

= 0.9.0 =
* Fixed: campaigns could get permanently stuck ("sending" forever, or "deferred"/"paused"
  and never resuming). Root causes removed rather than patched:
  - Background delivery no longer depends on the site being able to call its own URL
    (which fails on many Docker/port-mapped setups). A new heartbeat runs due work at
    the end of ordinary requests when the queue is stale, a token-protected tick URL is
    available for server/external cron (Settings → Background delivery), and the
    sleeping loopback "runner" was removed.
  - A reconciler runs every tick: recipients who unsubscribed or left the segment after
    being queued are marked skipped instead of holding the campaign open forever;
    campaigns left without a delivery job get one back; jobs that crashed without a
    lease are recovered; a job is only marked failed after 3 consecutive failing ticks.
  - One clock per sending account: the daily counter, its reset and the "resumes at"
    time now all use the account's timezone (or the site's). Previously three
    different "midnights" could push a capped campaign back a further 24 hours.
  - Per-account counters and circuit-breaker state moved out of the settings option
    into their own table and are updated atomically, so overlapping ticks can no longer
    overwrite each other's counts or pauses. Editing a sending account's settings no
    longer resets its warm-up day or counters.
  - Circuit breaker: the failure streak resets once a pause expires; broken templates
    and missing contacts no longer count against the sending account; "no sending
    account enabled" is reported as such (15-minute retry) instead of "daily cap
    reached" until midnight.
* Fixed: wake-up and re-engagement emails are now sent before campaign batches in every delivery tick and are checked every tick, so a new lead's wake-up email is no longer delayed by a running campaign.
* Changed: removed the `optcatch_sender_defer_seconds` filter (circuit-breaker/no-sender
  deferrals are a fixed 15 minutes).
* Changed: recipients whose send was interrupted mid-delivery are marked failed with error
  code `uncertain_delivery` instead of being re-sent.
* Fixed: a campaign whose segment was deleted now ends as Failed with the reason shown,
  instead of retrying forever.
* New: Reset warm-up & counters button per sending account.
* Changed: on servers without PHP-FPM, inline background delivery runs only for
  administrator requests; use the cron URL or a server cron otherwise.
* Background delivery now also reaches the site over 127.0.0.1 when WordPress's own
  self-request to the public URL fails (common inside containers and behind port
  mappings/NAT), so no server configuration is needed. The local port is discovered
  and verified by an admin-side check (never taken from the visitor's request); the
  verified port is remembered and re-verified by administrator visits. Settings →
  Background delivery shows the result with a "Re-check now" link.
* Note: campaigns that were stuck before this update will resume automatically about
  15 minutes after upgrading. Delete any old test campaign you do not want sent.
* Database schema v12 (new table optcatch_sender_state; one-time copy of counters).


= 0.8.15 =
* Enhanced: Replaced dynamic date-math warm-up counter with an autonomous static counter (sent_today, total_sent, warmup_day, last_sent_date) on each sending account, eliminating timezone drift and midnight calculation discrepancies.
* Enhanced: Wake-up and welcome emails now count toward daily warm-up limits and account counters on every send.
* Added: Immediate (0 delay) delivery preset for welcome emails on lead capture with autonomous runner dispatch, guaranteeing timely delivery on quiet servers.
* Improved: Concurrency and race-condition protection across sender account patches, test sends, and date rollovers via MySQL advisory locks.
* Fixed: Added missing database columns (error_code, next_retry_at) to table schema and activator migration routine.

= 0.8.12 =
* Fixed: the daily wake-up sweep never coordinated with the campaign-tick pool dispatch lock,
  so two overlapping sends (a wake-up sweep racing another wake-up sweep, or racing a campaign
  tick) could each read the same not-yet-recorded per-account budget and both send, letting an
  account's total for the day run past its warm-up cap. Wake-up dispatch now serializes under
  the same dispatch lock campaign sends already use.
* Fixed: the admin "Send a Test Email" / "Send a Test Wake-Up Email" fallback (used only when
  every account is unavailable to the normal picker) always used the first enabled account
  regardless of its cap, so repeated test sends kept compounding an already-over-cap account's
  overage. It now prefers any enabled account that still has room today, only falling back to
  an over-cap account if literally every enabled account is already there.

= 0.8.11 =
* Fixed: the campaign builder's "how long will this take" estimate excluded a sending
  account entirely the moment it was temporarily circuit-breaker paused, instead of still
  counting its real daily capacity. A brief pause (typically under an hour) on a high-volume
  account could make the estimate fall back to only a much smaller account's rate, reporting
  weeks for a delivery that would realistically finish the same day once the pause cleared.

= 0.8.10 =
* Fixed: the wake-up/re-engagement daily fallback sweep (catches contacts whose one-shot
  reminder cron event already fired and found no sender available) could burn through far
  more sends in one burst than a sending account's per-tick pacing was ever meant to allow —
  unlike a campaign send, which stays within one shared budget for its whole batch, the sweep
  re-checked "is there room for one more" independently for every contact in its queue, with
  nothing capping the total across that loop. This could immediately re-trip an account's
  circuit breaker that had just been sending normally moments before. The sweep now computes
  its available pool budget once, the same way a campaign tick does, and defers the rest of
  its queue once that's used up instead of continuing to send.

= 0.8.9 =
* Fixed: a campaign could be needlessly deferred until the next daily cap reset (up to 24
  hours) instead of a short retry, whenever one sending account was genuinely at its daily
  cap while a DIFFERENT account was only temporarily circuit-breaker paused. The deferral
  reason is now based on whether ANY account is merely paused (short wait, since it may
  recover within the hour) rather than stopping at the first account found NOT paused
  (which could be the capped one, ignoring a paused-but-otherwise-available account
  entirely).

= 0.8.8 =
* Changed: the Dashboard's warm-up widget now lists every enabled email address under a
  sending account ("Also sends as: ...") instead of showing only the first one. Warm-up and
  the daily cap are tracked per account (the connection that actually builds sending
  reputation), not per address, so an account with several From-addresses configured on
  purpose (e.g. support@/sales@/hello@) now makes that clear instead of making it look like
  only one address was ever set up.

= 0.8.7 =
* Fixed: a send whose SMTP server rejected the message body ("Data not accepted") could leave
  that connection open mid-transaction, so the NEXT message sent through the same account in
  the same request/tick failed with an unrelated "503 5.5.1 nested MAIL command" error and
  unfairly counted toward that account's failure count. The mailer now force-closes any
  leftover SMTP connection before configuring each send, so one rejected message can no longer
  corrupt the one after it.

= 0.8.6 =
* Fixed: a form with ONLY an explicit `.lcc-consent` checkbox (no `lcc-lead-form` class or
  `data-lcc-capture` attribute anywhere on the form or a wrapper) was silently never scanned
  for capture, even though placing that class is itself a deliberate opt-in signal from the
  site owner. A form is now opted in if it has EITHER marker, matching how it already reused
  an existing checkbox once a form was otherwise marked. Unrelated forms (search boxes, login,
  comments) without any of these markers remain completely untouched.

= 0.8.5 =
* Fixed: a deleted campaign could still appear under the Dashboard's "Recent campaigns" (the
  query there didn't exclude soft-deleted rows the way the main campaign list already did).
* Fixed: rolling automation campaigns (delay-after-capture) weren't shown anywhere on the
  Dashboard even while active — they now appear under "Rolling automations" with their
  queued-contact count and earliest next send time.
* Changed: the campaign list and the per-recipient drill-down now show "Retrying at HH:MM"
  for a recipient that has already failed once and is backing off before its next attempt,
  instead of a generic "Pending / retrying" that reads the same as "not attempted yet".
* Hardened: the Campaigns admin page no longer risks a fatal "critical error" screen if a
  segment's delivery-time estimate or a campaign's stats fail to compute — it now degrades
  that one item gracefully and logs the failure instead of breaking the whole page.

= 0.8.4 =
* Changed: campaign send pacing now scales automatically with the campaign's own recipient
  count instead of always spreading an account's daily allowance over a fixed 12-hour window.
  A small campaign on a well-warmed account now completes in minutes rather than being
  needlessly trickled out for hours; a large campaign is paced over a longer window, and any
  volume beyond what an account can safely send in one day is delivered automatically on the
  following day(s) — the daily cap and warm-up limits still apply exactly as before and remain
  the hard ceiling on total volume, this only changes how quickly a day's already-approved
  volume goes out. The campaign builder's "how long will this take" estimate and the delivery
  timing notes now explain why sends are paced instead of going out all at once.

= 0.8.3 =
* Fixed: an admin diagnostic test send (Wake-Up "Send a Test Wake-Up Email" or Settings "Send
  Test Email") that bypasses every sending account being at its daily cap now says which
  account it actually went out through, and is logged under a distinct marker so it counts
  toward that account's real daily total instead of being invisible to warm-up/reputation
  tracking.

= 0.8.2 =
* Fixed: forms whose consent control is a native WPForms checkbox field (instead of the
  plugin's injected one) are now recognized — the checkbox's configured CSS class (e.g.
  "lcc-consent") is read from the field's own WPForms configuration when the plugin's own
  injected field isn't present on the form.
* Fixed: Wake-up follow-ups and re-engagement emails now correctly rotate between sending
  accounts. The account-selection sort previously compared each account's per-tick-capped
  budget, which collapses to the same value across accounts whenever a small daily cap makes
  that cap the binding constraint for all of them — ties then always resolved to the same
  account. Sorting by true remaining daily capacity instead means using an account lowers its
  priority for the very next pick.
* Fixed: sending a Wake-Up test email (or a Settings test email) while every sending account
  is at its daily cap no longer fails with a raw "Could not instantiate mail function" error
  from the site's broken default mail transport — it now bypasses the cap through an enabled
  account, same as the existing Settings test-email behavior, and is never counted against
  the real daily cap.
* Fixed: a wake-up/re-engagement email deferred for lack of sending-account budget now shows
  up in the contact's Email history as "Deferred (daily cap), resumes …" instead of the
  contact appearing to have no email history at all.
* Internal: added form 16 (native-checkbox consent) coverage to the automated WPForms capture
  regression test.

= 0.8.1 =
* Fixed (Blocker): WPForms submissions stopped creating contacts. WPForms' wpforms_process_complete
  hook does not pass raw $_POST despite its own documentation claiming otherwise — the plugin's
  injected consent checkbox and honeypot field are read from $_POST directly now, not from the
  hook's own reshaped $entry parameter. Added an automated regression test
  (tests/wpforms-capture-test.php) that encodes the real, observed shape of WPForms' hook
  parameters so this can't silently regress again.
* Fixed: Recipients deferred by a daily-cap pause now show "Deferred — daily cap (resumes …)"
  instead of "Pending / retrying (no account recorded)", with a "Deferred" status filter; the
  campaign list's count column shows "N sent · M deferred" instead of a confusing
  "N sent / total … sending" combination.
* Fixed: The campaign Email Subject field no longer pre-fills with the template's default subject
  on a fresh "Create New Campaign" load — only a placeholder is shown, so typed text always
  replaces it cleanly instead of getting appended to preset text.
* Improved: The Dashboard's Campaign Activity KPI is now explicitly labeled "Campaign emails" with
  a separate "Automation" figure alongside it, instead of silently excluding Wake-Up/re-engagement
  sends from what looked like an overall "Sent" total.

= 0.8.15 =
* Improved: Site-wide mail routing ("Use Primary Sending Account for all WordPress email") is now enabled by default ('1') so Contact Form 7, WPForms, Elementor Pro forms, and WordPress core notifications deliver seamlessly as soon as a sending account is configured, without requiring manual setting changes.
* Improved: Site-wide WordPress emails now route strictly through the Primary Sending Account (Account #1) rather than rotating across cold outreach campaign inboxes, and do not consume marketing campaign quotas.
* Improved: Added "★ Primary / Default WordPress Mailer" badge and helpful connection tips to Account #1 in Admin.

= 0.8.0 =
* Fixed: An hourly-pacing side effect could zero out a sending account's tick budget even
  when its own daily cap still had room, causing an entire immediate campaign to defer for
  a fixed 30 minutes with nothing sent. The account cap and the Dashboard's own count now
  agree; a partially-throttled campaign shows "Partially sent — N deferred (daily cap)" and
  resumes at the next daily reset instead of retrying blindly.
* Fixed: Wake-up follow-up, re-engagement, and admin test-send emails now go through the
  same sending-account rotation, warm-up/daily-cap accounting, and circuit breaker as
  campaign sends, instead of always using the first enabled account regardless of its
  health. They're deferred (not sent) when no account currently has budget.
* Fixed: A contact could receive a duplicate wake-up follow-up if a worker was interrupted
  between sending and recording it. Each contact now has a durable per-type "already sent"
  marker checked before every send attempt (schema v9).
* New: A per-campaign "Recipients" view (email, status, sending account, From address,
  timestamp, error; filterable) and a per-contact "Email history" panel on the Contacts
  screen, covering both campaigns and wake-up/re-engagement sends.
* New: WPForms submissions are now captured only after WPForms' own spam/honeypot checks
  accept them, instead of on every raw form submit attempt.
* New: Optional "Use Lead Capture accounts for all WordPress email" setting, so other
  plugins' own notification emails (e.g. Contact Form 7) can be routed through a configured
  Sending Account instead of silently failing on a broken default mail transport.
* Improved: Campaign subject field now shows a template's default text as a placeholder
  instead of pre-filling it; Wake-Up test-email field defaults to the site admin email; CSV
  import treats a duplicate email within the same file as skipped (keeps the first row);
  CSV export writes tags as a comma-separated list instead of raw JSON.
* Internal: contacts table gained followup_sent_at/reengage_sent_at (schema v9).

= 0.7.0 =
* New: A hard, always-on per-account "max per batch" safety cap (default 5, configurable)
  that spaces individual sends out over separate delivery cycles instead of ever firing a
  whole batch back-to-back — this now applies even if warm-up is turned off or a very high
  daily cap is set, closing the one scenario where sending could still burst.
* New: The campaign builder now shows an estimated delivery-time window for the selected
  segment (e.g. "about 3 hours", "about 2 days"), and makes clear that "Send Immediately"
  and scheduled sends both mean the campaign *starts* then, not that every recipient
  receives it at that moment.
* Improved: Sending accounts have a new "Max per batch" setting under Warm-up & daily cap.

= 0.6.1 =
* Added a required "External services" disclosure (Freemius, SendGrid, Google, Microsoft
  365) and corrected a line in the data-collection section that incorrectly claimed no data
  is ever sent to an external service.
* Documented the free plan's contact (500) and CSV import (100 rows) limits in the Free vs.
  Pro section.
* Shortened the plugin's short description to fit WordPress.org's length limit and bumped
  "Tested up to."
* Internal: uninstall now also clears the two cron hooks directly (defense-in-depth for an
  uninstall performed without a prior deactivate step); added a missing translator comment.

= 0.6.0 =
* New: Delivery failures are now recorded and visible. Each failed (or retrying) send stores
  its error code/message and sending account; the campaigns list links to a per-campaign
  "Delivery failures" panel grouped by error, with Retrying vs Failed status and counts per
  account (schema v8).
* New: Failed sends are classified as temporary (rate limits, timeouts, greylisting) or
  permanent (invalid recipient, auth failure). Temporary failures are retried with
  exponential back-off (5 min, 15 min, 1 hour, next day) instead of being marked failed
  immediately; permanent failures are marked failed right away.
* New (Pro): Circuit breaker per sending account — after repeated failures (or on the first
  authentication failure) an account is paused for 1 hour, then 24 hours if it recurs, and
  the queue automatically routes to the next healthy account. The Dashboard warm-up card
  shows each account's Active / Paused until… / Error state, and an admin notice/email is
  sent when an account is paused.
* Improved (Pro): Warm-up daily caps are now enforced before sending, not just displayed.
  When every account's cap (or a paused account) blocks sending, remaining recipients are
  deferred to the next tick/day ("Deferred" / "Paused") instead of being marked failed, and
  rotation now picks the account with the most remaining budget first.
* Fixed: Deleting a campaign no longer erases its send/failure history — campaigns are
  soft-deleted so Dashboard stats and warm-up counters stay accurate.
* Fixed: A campaign that finished sending (with or without failures) now correctly shows
  "Sent" or "Sent with errors" instead of staying stuck on "Sending".
* Fixed: A phantom "1 Scheduled" could remain on the Dashboard after deleting a campaign
  with pending queue rows.
* Improved: The warm-up card now shows each sending account's display name and From address
  instead of its internal ID.
* Internal: campaigns table gained deleted_at and a wider status enum; sends table gained
  error_code and next_retry_at (schema v8). Also fixed a pre-existing gap where a fresh
  install's campaigns table was missing several columns already in use.

= 0.5.0 =
* New (Pro): Multiple sending accounts — add several From addresses on one account, or
  separate mailboxes each with their own credentials (Gmail app-password / SMTP / SendGrid),
  with automatic rotation across the pool. Free stays on a single global sender.
* New (Pro): Per-account warm-up plans and drip throttling — daily allowances ramp up over
  days to protect sender reputation, and sends are spread out instead of all at once.
* New: Two-mode Wake-Up emails. The wake-up automation is now two independent emails, each
  with its own subject and template, and a contact can receive both once each:
  * Immediate follow-up (Free) — minutes/hours/days after a lead is captured (unchanged).
  * Re-engagement (Pro) — a longer-horizon win-back email days/weeks/months after capture,
    with its own starter template.
* Improved: Wake-Up settings are reorganised into clear "Immediate follow-up" and
  "Re-engagement" cards, and the dashboard Wake-Up card now reports both modes. Test-send
  and Run-Now can target either mode.
* Improved: the Sending settings now warn when SendGrid is selected but no API key is set.
* Internal: sends now records which sender delivered each email (schema v7).

= 0.4.6 =
* Improved: the dashboard Realtime graph now shows emails sent per 5 seconds over the last
  2 minutes as a live area chart, with a seconds axis that advances every poll and a cursor
  on the current bucket. It is now the same size as the Campaign Activity chart.

= 0.4.5 =
* New: SendGrid sending provider. Paste a SendGrid API key (Mail Send permission) to
  deliver campaigns and notifications over the SendGrid HTTPS API — higher deliverability
  for bulk email and works even on hosts that block outbound SMTP ports. Selectable
  alongside the existing Google (Gmail), Microsoft 365 / Outlook, App Password and custom
  SMTP options. Your From address must be a verified SendGrid sender or authenticated domain.

= 0.4.4 =
* New: Wake-Up emails can now follow up minutes or hours after a lead is captured, not
  only after a number of days. Choose a value and a unit (minutes / hours / days) under
  "Send this long after a lead is captured." When a lead consents, their single wake-up
  is scheduled to send after that delay; the daily sweeper remains a fallback that also
  picks up quiet consented leads who reached the threshold.
* Each contact still receives at most one wake-up email (idempotent), whether it is
  delivered by the per-lead schedule or the daily sweeper.
* The dashboard Wake-Up card and the settings screen now show the delay with its unit
  (e.g. "2 minutes after capture").

= 0.4.3 =
* Fixed: a form submission could trigger a duplicate capture request where one returned
  HTTP 500. The capture endpoint is now idempotent — a concurrent/repeat submission for
  the same email updates the existing contact (filling in blanks such as the name) and
  never errors; consent is never downgraded.
* Fixed: the visitor's name is now captured from single full-name fields (e.g. Contact
  Form 7 "your-name") and from autocomplete hints, in addition to split First/Last
  fields; a full name is stored as First + Last.
* The final capture per submission is now sent at most once.

= 0.4.2 =
* Fixed: first name / last name were not captured from form builders that split a Name
  field into separate "First" and "Last" sub-fields (e.g. WPForms). Field detection now
  recognizes the standalone "First"/"Last" labels and field names in addition to
  "First name"/"firstname"/"fname" style names. Email capture was unaffected.

= 0.4.1 =
* Fixed: the consent checkbox failed to appear on WPForms (and other builders whose
  submit button sits inside a wrapper element) because the injection assumed the
  submit button was a direct child of the form. It now inserts relative to the
  button's actual parent, and a form is only marked "scanned" after its controls are
  attached so a one-off failure can no longer permanently skip it.
* Fixed: the background job worker's recurring event now self-heals on every load, so
  a lost/never-scheduled cron can no longer leave campaigns stuck in "sending" with
  nothing delivered. (Previously it was scheduled only on activation.)

= 0.4.0 =
* New: interactive dashboard "Campaign Activity" chart — daily sent/failed timeline with a
  date-range filter (7/30/90 days, year, all, or a custom range), click-a-bar drill-down to
  that day's campaigns, and an upcoming scheduled-sends list. Fully self-contained, no
  external scripts.
* New: single "Your Company Name" setting drives the {{company}} merge tag everywhere. The
  per-contact company field has been retired from the UI for single-business use.
* New: the free plan can now save up to 2 segments (previously Pro-only); editing existing
  segments is always allowed. Pro remains unlimited.
* Changed: segment Email/Name filters now match "contains" instead of "starts with"
  (segments saved earlier keep their original matching).
* New: a clear plan section in Settings shows which plan is active (Optcatch Pro is a separate
  plugin from optcatch.com).
* Fixed: reconciled the campaign job and campaign status columns with the values the code
  uses, restoring reliable campaign delivery (a prior schema mismatch could stall sends).
* Changed: the multi-tenant Customer/Owner Scope switcher is hidden by default (enable via
  the `optcatch_enable_multi_tenant` filter).

= 0.3.0 =
* Optcatch Pro (a separate plugin) is sold and licensed through Freemius; the free plugin
  only links to the Pro product page.
* Removed the manual license-key field and the placeholder external upgrade link.

= 0.2.4 =
* Form capture is now opt-in: the plugin only attaches to forms you mark with the `lcc-lead-form`
  class or `data-lcc-capture` attribute (on the form or a wrapper). It no longer injects the
  consent checkbox into unrelated forms such as the theme's search box.
* You can reuse your own privacy checkbox by adding the `lcc-consent` class to it; the plugin then
  skips injecting its own.
* Added settings for the lead-form and consent checkbox class names, plus the
  `optcatch_lead_form_selector` and `optcatch_consent_checkbox_selector` filters.
* Fix: consent checkbox could appear on non-lead forms (e.g. header search box).
* Enforced Pro gates that were previously unenforced: wake-up "all sources" targeting and the
  free contact limit on form capture. Custom email template creation is now a Pro feature
  (editing/deleting existing templates remains available).

= 0.1.0 =
* Initial version: form capture, CSV/manual import, segment builder, WP-Cron send worker,
  template-based email builder, campaign builder, admin dashboard, one-click unsubscribe.
