=== Nectazo Connect ===
Contributors: serks
Tags: email marketing, newsletter, woocommerce, smtp, subscribers
Requires at least: 5.7
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.17.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Send your WordPress and WooCommerce customers to Nectazo, and optionally send this site's email through it too.

== Description ==

Nectazo Connect links this site to [Nectazo](https://www.nectazo.com), a hosted
email marketing service, so the people who sign up or buy here become contacts
you can email — without exporting a CSV every month.

**You need a Nectazo account.** The plugin is the bridge; the sending, contact
storage, campaigns and reporting all happen in the service. There is a free
plan, and the plugin is free.

Everything below is off by default. Turn on only what you want.

**Bring people in**

* New WordPress user registrations become contacts.
* New WooCommerce customers become contacts.
* Each person can be added to a specific list.
* Anyone who has unsubscribed stays unsubscribed — the plugin cannot resurrect
  an opt-out, however many times a contact is synced.

**WooCommerce Subscriptions**

If you run the Subscriptions extension, each customer's current state is
written onto their contact: whether they have an active subscription, which
product, whether it is billed monthly or annually, and whether a coupon is
applied. That makes segments like "everyone not on an annual plan" or "annual
subscribers paying full price" possible without exporting anything.

**Products in your emails**

Your catalogue is pushed to Nectazo so products can be dropped into a campaign
with live titles, images and prices. Products are pushed from this site rather
than pulled into it, so hosting firewalls and bot protection never get in the
way and there are no REST API keys to generate.

**Orders and revenue**

Your WooCommerce orders are sent to Nectazo so a campaign can report the money
it actually earned, and so segments can ask what somebody bought — everyone who
has ordered in the last 90 days, everyone who bought a particular product,
everyone who has spent over a certain amount.

Totals and line items only. No addresses, no payment details, and sending an
order never creates a contact: buying something is not the same as asking to be
emailed. An order from somebody who is not already a contact still counts
toward your revenue; it simply is not tied to a person.

**Abandoned carts**

If somebody fills a cart and leaves, Nectazo can remind them — with the actual
items, and a link that puts the cart back so they carry on where they stopped.
Buying stops the reminder automatically.

Two honest limits. It only ever emails people who are already subscribed
contacts: a cart is not consent, any more than an order is. And a shopper who
leaves before typing an email address cannot be reached by anybody, so the
carts you can recover are the ones abandoned at or after checkout.

**Website visitors**

Counts visits to this site and shows them on your Nectazo dashboard: how many
people, where they came from, which pages, and who is on the site right now.
Works with WordPress on its own &mdash; no store required.

No cookies, and nothing at all is stored on a visitor's device, so this needs no
consent banner. No IP addresses and no browser details are kept. Visitors cannot
be followed from one day to the next, which means daily visitor counts are
honest and a longer "unique visitors" figure is deliberately not offered rather
than estimated.

**Transactional email**

Optionally, this site's password resets, order receipts, notifications and
WooCommerce emails — anything `wp_mail()` sends — go out through Nectazo's
verified, DKIM-signed domain instead of your host's mail server. Every send is
logged and previewable.

If Nectazo cannot be reached, WordPress's own delivery runs exactly as if the
plugin were not installed. Nothing is silently dropped.

**Signup forms**

`[nectazo_form id="..."]` embeds a form built in Nectazo's form editor —
inline, popup, slide-in or bar — anywhere a shortcode works.

Popups that should be available everywhere go in the "Site-wide forms" setting
instead, which lists your published forms and loads the ones you tick on every
page. That is what you want for a form set to open when a button is clicked,
since the button can be anywhere: add `data-nectazo-open` to any button or link
and it opens the form. More than one can be ticked.

== External Services ==

This plugin connects to Nectazo, a third-party email service operated by Oynayo
Pty Ltd. It will not work without a Nectazo account, and nothing is sent
anywhere until you enter an API key and enable a feature.

What is transmitted, and when:

1. **Contact sync** — when someone registers, or a WooCommerce customer is
   created, or you press one of the sync buttons. Sends the person's email
   address, first and last name, a source label, and your chosen list id.

2. **Subscription sync** (optional, requires WooCommerce Subscriptions) — when
   a subscription is created or changes status, or you press "Sync all
   subscriptions now". Sends the customer's email address and their current
   subscription status, product name, billing period and any coupon code.

3. **Product sync** (optional) — when a product is added or edited, or you
   press "Push all products now". Sends this site's URL along with each
   product's name, description, price, image URL, product URL, SKU and status.

4. **Transactional email** (optional, off by default) — when enabled, every
   message `wp_mail()` sends is transmitted to Nectazo for delivery. This
   includes the **recipient address, subject line and the full body of the
   email**, which for password resets includes the reset link. Turn this on
   only if you are content for your site's outgoing mail to be handled and
   logged by Nectazo.

5. **Signup forms** — the `[nectazo_form]` shortcode, and any form ticked
   under "Site-wide forms", load a script from `https://www.nectazo.com` in
   your **visitor's browser**. That request reaches Nectazo from the visitor's
   device, so their IP address and user agent are visible to it, along with
   anything they type into the form. The shortcode does this only on the pages
   it appears on; a site-wide form does it on every page.

6. **Listing your forms** — when you open the plugin's settings screen, it asks
   Nectazo for the names of your published forms so it can show them as
   checkboxes. This is an admin-side request using your API key, cached for a
   few minutes, and involves no visitor data.

7. **Order sync** (optional, requires WooCommerce) — when an order is placed or
   changes status, or you press "Send past orders". Sends the order's id, date,
   status, currency, total, each line item's product id, name, quantity and
   price, and any coupon codes used, along with the customer's email address
   where the order has one. Never the billing or shipping address, and never
   payment details.

8. **Abandoned carts** (optional, requires WooCommerce) — when a shopper who
   has given an email address adds to their cart or reaches checkout. Sends
   that email address, the cart's items and total, and a random recovery token.
   Nothing is sent for a shopper who has not identified themselves, because
   there would be nobody to send it to.

9. **Website visitors** (optional, off by default) — when enabled, every page
   of this site loads a few lines of inline JavaScript that send a request from
   your **visitor's browser** to `https://www.nectazo.com`. It carries the page
   path, the referring site where there is one, and any `utm_` parameters in
   the address. As with the form script above, that request reaches Nectazo
   from the visitor's own device, so their IP address and user agent are
   visible to it in the process — Nectazo does not store either, and stores
   nothing on the visitor's device, but the request is made and you should say
   so in your privacy policy. Unlike the form script, this happens on every
   page rather than only where a form appears.

10. **Review tagging** (optional, requires the Nectazo Reviews plugin with a
    tag configured) — when a review is submitted, sends the reviewer's email
    address and that one tag, so a Nectazo automation can react to it. Nothing
    is sent for a review left without an email address.

11. **Coupon minting** (optional, off by default) — when "Let Nectazo create
    coupons" is on, Nectazo may call this site to create a WooCommerce coupon
    as part of an automation. This is inbound: the request carries the coupon
    terms and the customer's email address (to restrict the coupon to them),
    authenticated by your site key, and this site creates the coupon locally.
    No customer data leaves your store for this; it is Nectazo asking your store
    to act.

Service terms: https://www.nectazo.com/terms
Privacy policy: https://www.nectazo.com/privacy

== Installation ==

1. In Nectazo, switch to the brand this site should feed, go to
   **Settings -> API keys**, and create a key. Copy it — it is shown once.
2. Optionally copy a **List ID** (also under Settings -> API keys) if new
   signups should join a particular list.
3. Install this plugin and activate it.
4. Go to **Settings -> Nectazo**, paste the API key, choose a list if you want
   one, and save.
5. Press **Send test contact**. The address you enter should appear as a
   contact in Nectazo within a second or two.
6. Turn on whichever of the sync options you want. They are all off until you
   do.

== Frequently Asked Questions ==

= Do I need a paid Nectazo account? =

No. There is a free plan, and this plugin is free. You do need an account of
some kind — the plugin sends data to the service and cannot store contacts or
send email by itself.

= Where do new users get added? =

To the Nectazo brand that issued the API key you pasted in. One site feeds one
brand. To feed a different brand, create a key in that brand — typically on a
different site.

= Does it re-subscribe people who unsubscribed? =

No. Nectazo ignores contacts already suppressed in that brand, so a repeated
sync can never put somebody back on a list they left.

= Can I stop certain users from syncing? =

Yes. Return false from the `nectazo_connect_should_sync` filter for any email
address you want skipped.

= What happens to transactional email if Nectazo is down? =

WordPress delivers it normally. The takeover only short-circuits `wp_mail()` on
a confirmed send, or a deliberate skip for a suppressed address. Any error,
timeout or unreachable API leaves `wp_mail()` to run exactly as if the plugin
were not there.

= Does it track clicks in my password reset emails? =

No. Links in transactional email are sent exactly as your site wrote them. A
reset key can only be used once, and putting a redirect in front of it means
any link scanner that inspects the message spends the key before your customer
does.

= Will it slow down registration or checkout? =

No. Live syncs are sent without waiting for a reply, so a slow or unreachable
API cannot hold up somebody signing up or paying.

== Upgrade Notice ==

= 1.9.0 =
Pick site-wide forms from a list instead of pasting an id, and load more than
one. Existing settings carry over.

= 1.7.0 =
Adds a button to create contacts for subscribers who are not on your list yet.
Existing behaviour is unchanged.

== Changelog ==

= 1.17.0 =
* Security: coupon minting now uses a dedicated server-to-server secret instead
  of the site key. The site key is a public identifier printed in the traffic
  beacon, so authorising coupon creation with it could let a visitor mint
  coupons. The new secret is fetched from Nectazo, never sent to the browser,
  and is what the /mint-coupon endpoint checks. Addresses a WordPress.org plugin
  review finding. No action needed — the plugin provisions the secret itself.

= 1.16.1 =
* Hardening: values placed inside the inline traffic and cart-email scripts
  are now encoded with JSON_HEX_TAG, so no value can ever close the script
  element early. Addresses a WordPress.org plugin review note.

= 1.16.0 =
* The subscription plan, period and coupon fields are now also filled for
  on-hold and pending subscriptions, not only active ones. A member whose
  renewal payment is failing (on-hold) keeps wc_subscription_period, so you can
  segment "monthly subscribers on hold" instead of seeing a blank period.
  Cancelled and expired subscriptions still clear those fields, as before.
  Run "Sync subscriptions" after updating to backfill existing contacts.

= 1.15.0 =
* Coupons a Nectazo automation issued are marked redeemed when the customer
  uses them, from the order sync. And a review left through Nectazo Reviews now
  tags the reviewer's contact, so an automation can reward it.

= 1.14.0 =
* Nectazo can now create a WooCommerce coupon on your store when an automation
  issues one (for example, to reward a review). Turn on "Let Nectazo create
  coupons" in Settings. The terms are set in the automation; the code is made
  here, restricted to the customer's own email so it cannot be shared. The
  request is authenticated by your site key.

= 1.13.0 =
* Product sync now tells Nectazo which products are subscriptions. A "Someone
  buys" automation can then be pointed at non-recurring products only — so a
  review request fires on one-off purchases and never on a subscription or its
  renewals. Re-sync your catalogue (Settings → Nectazo) after updating.

= 1.12.0 =
* Website visitor counts. Turn it on and your Nectazo dashboard shows visits,
  traffic sources, top pages and a live count of who is on the site right now.
  Off by default, and it works without WooCommerce.
* No cookies and nothing stored on a visitor's device, so no consent banner is
  needed. There is deliberately no setting that would change that.

= 1.11.2 =
* Carts filled while logged out are now attached to the shopper when they sign
  in at checkout. Previously the cart hooks only fired when the contents
  changed, so signing in — which changes who the cart belongs to but not what
  is in it — left the cart belonging to nobody and no reminder was possible.
* A guest's typed email address now actually sends the cart. The capture stored
  the address and then stopped short of pushing, because WordPress reports
  admin-ajax requests as admin requests and the push skips those.

= 1.11.1 =
* A recovery link now puts back the exact variation the shopper chose. Products
  with options were being rebuilt from the parent product alone, which drops the
  choice and which WooCommerce will not add at all.

= 1.11.0 =
* Abandoned carts. A cart left behind can start an automation in Nectazo that
  shows the shopper what was in it and links them back to it. Off by default.
* Following that link rebuilds the cart on this site and goes straight to
  checkout, so nothing has to be found again.
* Completing an order marks the cart recovered and stops the reminder, matched
  by the cart itself rather than guessed from the email address and the time.
* Only subscribed contacts are ever emailed, and a cart abandoned before an
  email address is entered is never sent anywhere.

= 1.10.1 =
* A rejected batch now reports one readable line instead of the raw response.
  Nectazo returns a validation issue per item, so a batch of fifty failing the
  same way filled the settings screen with several thousand characters saying
  one thing.

= 1.10.0 =
* Orders are sent to Nectazo, so a campaign report can show the revenue it
  earned and segments can ask what somebody bought. Off by default.
* A "Send order history" button sends the last 12 months in one go — without
  it, a segment like "bought in the last 90 days" finds nothing until somebody
  orders again.
* Order updates are sent on every status change, so a refund takes the revenue
  back down rather than leaving the campaign credited for money that was
  returned.

= 1.9.1 =
* Each click-triggered form now shows the exact button attribute to use,
  including its id. With more than one form on a page the id is what ties a
  button to one of them — a bare `data-nectazo-open` opens all of them.

= 1.9.0 =
* The site-wide setting is now a checkbox list of your published forms, read
  from Nectazo, instead of a box to paste a form id into. Nobody should have to
  copy a UUID between browser tabs, and drafts simply do not appear.
* More than one form can load site-wide. Two forms opened by their own buttons
  is an ordinary thing to want; two that open by themselves is not, and the
  settings screen says so rather than refusing.
* Settings saved with 1.8.0 are read as a one-item list, so nothing needs
  re-picking after updating.

= 1.8.0 =
* Add a "Site-wide form" setting. The shortcode only loads a form on the pages
  it appears on, which is right for an inline form and wrong for a popup opened
  by a button in the header — the only way to get one everywhere was to park an
  invisible shortcode in a footer widget. Set a form id here and it loads on
  every page instead. Setting both for the same form is harmless; it loads once.

= 1.7.0 =
* Add an "Add missing subscribers as contacts" button. Subscription sync fills
  fields in and never adds anybody, so subscribers who are not on the list were
  reported and left alone; this adds them, carrying their subscription fields
  with them. Anyone who previously unsubscribed stays unsubscribed, and the
  brand's own double opt-in setting decides how the rest arrive.

= 1.6.1 =
* Make the sync's time budget actually apply. It was only checked between
  batches, so a store whose subscribers all fit in one batch never consulted it
  and could be killed by PHP part-way through — which looks identical to
  success. It is now checked per customer.

= 1.6.0 =
* One click now syncs every subscriber, instead of 50 at a time. The backfill
  sends whole batches to Nectazo in a single request rather than one request
  per customer, so a store with a few hundred subscribers finishes in seconds.

= 1.5.2 =
* Say next to the sync button that the four custom fields do not need creating
  in Nectazo first, name them, and spell out the one thing the sync does
  require: that the customer is already a contact in that brand.

= 1.5.1 =
* The "Sync all subscriptions now" button now waits for Nectazo's answer and
  reports what actually happened — how many contacts were updated, and how many
  subscribers are not contacts in that brand yet. It previously reported success
  without checking, so a run that changed nothing looked identical to one that
  worked.

= 1.5.0 =
* Sync WooCommerce Subscriptions state to Nectazo contacts. Adds four custom
  fields — wc_subscription, wc_subscription_plan, wc_subscription_period and
  wc_subscription_coupon — so segments can target, for example, everyone who is
  not on an annual plan, or annual subscribers paying full price. Updates
  existing contacts only; it never creates new ones. Includes a "Sync all
  subscriptions now" button for the initial backfill.

= 1.3.0 =
* Sync WooCommerce products to Nectazo for use in the Products email block.
  Products are pushed from this site rather than pulled into it, so hosting
  firewalls and bot protection never get in the way and there are no REST API
  keys to generate. Individual products are sent automatically when added or
  edited, plus a "Push all products now" button for a full refresh.

= 1.2.0 =
* Add an optional transactional-email takeover: send this site's password
  resets, order receipts, and all other `wp_mail()` traffic (including
  WooCommerce) through Nectazo, with automatic fallback to WordPress's own
  delivery if Nectazo is unreachable. Includes a test-send tool.

= 1.1.0 =
* Add a `[nectazo_form id="..."]` shortcode to embed Nectazo forms anywhere on
  the site.

= 1.0.0 =
* Initial release: sync WordPress registrations and WooCommerce customers to a
  Nectazo brand, with an optional target list and a test-connection tool.
