=== Emerge Mail ===
Contributors: redigit
Tags: gmail, oauth, smtp, transactional-email, sendgrid
Requires at least: 6.0
Tested up to: 7.0
Requires PHP: 8.1
Stable tag: 1.4.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Route wp_mail through Gmail, Microsoft, SendGrid, Mailgun, Amazon SES, Brevo, or any SMTP server. OAuth or bring-your-own-key, with multi-mailbox failover.

== Description ==

Emerge Mail replaces SMTP plugins for transactional WordPress email. Connect one or more mail accounts and every `wp_mail()` call your site makes — password resets, comment notifications, WooCommerce receipts, contact-form submissions — is delivered through that account.

Seven providers, one unified send path:

* **Gmail** (OAuth) — sign in to your Google account; no SMTP password needed.
* **Microsoft 365 / Outlook** (OAuth) — sign in to personal Outlook/Hotmail/Live or Microsoft 365 work/school accounts.
* **SendGrid** (API key) — paste a v3 API key with Mail Send permission.
* **Mailgun** (API key) — paste a private API key, sending domain, and pick your region (US or EU).
* **Amazon SES** (Sigv4) — paste an IAM access key + secret with `ses:SendEmail` permission. No AWS SDK dependency.
* **Brevo** (API key) — paste a v3 API key.
* **Generic SMTP** — host + port + encryption + username + password for any SMTP server (your own host, an SES SMTP endpoint, Postmark SMTP, Fastmail, mailcow, etc.).

Because the From address is your own authenticated mailbox or verified sender, messages benefit from SPF, DKIM, and DMARC alignment that PHP's `mail()` function cannot provide. This typically results in dramatically better inbox placement than default WordPress mail.

= Key features =

* **Seven providers** (above), all sharing the same routing, failover, and From-email overrides.
* **Multiple connections per site** with automatic failover. The Active connection sends first; if it fails, the next available connection is tried, with a 1-hour cooldown on failed connections. Mix-and-match across providers — e.g. SendGrid primary, an SMTP fallback, and a Gmail mailbox as the last-resort backup.
* **Optional connection throttle**: rotate through all your connected accounts instead of hammering the Active one.
* **Per-connection From email override** for verified send-as aliases / verified senders.
* **Configurable sender defaults**: a custom From name and an opt-in "Reply-To = admin email when sender differs" setting.
* **Full `wp_mail` compatibility**: To/Cc/Bcc (including from headers), HTML bodies, multipart alternative, attachments, custom headers, RFC 2822 address formats including comma-separated lists and quoted display names, non-UTF-8 charsets.
* **RFC 2047 encoding** for non-ASCII subjects and display names.
* **One-click Send test** button per connection.
* **Email Controls** page: opt-in suppression of WordPress core self-notifications you don't want to receive (comment moderation, automatic updates, new-user registered, etc.) — recorded to the Activity Log instead.
* **Activity Log** page: unified record of mail sent through your connections, suppressed notifications, provider send errors, and recipient bounces — with provider, type, and severity filters, Provider / Mailbox / From / Recipient columns, bulk delete, and automatic 10-day pruning.
* **Bounce classification**: provider-specific error patterns map to a unified `send.bounce.*` / `send.error.*` taxonomy so per-recipient bounces are distinguished from transport outages across all seven providers.
* **Last-error visibility** per connection and a site-wide admin notice on recent failures.
* **Automatic hourly token refresh** for OAuth connections (Action Scheduler when available, WP-Cron fallback). API-key connections require no maintenance.
* **Safe fallback** to default WordPress mail delivery on any failure — the plugin never blocks email.
* **Extensible via filters**: cooldown, HTTP timeouts, token-refresh timing, MIME output, suppression catalog.

= How it works =

When you connect an account, the credentials (OAuth tokens, or your API key / SMTP password) are encrypted with a per-site key and stored as a WordPress option. From that point on, your site talks directly to the provider's API or SMTP endpoint; no third party sits in the send path.

The plugin hooks `pre_wp_mail`. If at least one connection is active, the message is routed through it. If the provider returns an error — or if no connections are configured — WordPress's default mail delivery takes over transparently.

= What this is not =

* Not a marketing or bulk email tool. No subscriber lists, no campaigns, no broadcasts.
* Not a shared-IP SMTP relay. Each site sends from its own connected accounts.
* Not a way to spoof arbitrary From addresses. The provider will reject anything that isn't an authorized sender on the connected account.

== Installation ==

1. Upload the `emerge-mail` folder to `/wp-content/plugins/`, or install via the Plugins screen.
2. Activate the plugin from the Plugins menu.
3. Click the new **Emerge Mail** top-level item in your WordPress admin sidebar.
4. Pick a provider and connect:
   * **Gmail / Microsoft** — click **Connect Gmail** or **Connect Microsoft** and approve the OAuth consent screen.
   * **SendGrid / Brevo** — paste an API key with Mail Send permission and a verified From email.
   * **Mailgun** — paste your private API key, a verified sending domain, and pick your region (US or EU).
   * **Amazon SES** — paste an IAM access key + secret with `ses:SendEmail` permission and the region your SES identity is verified in.
   * **Generic SMTP** — paste host, port, encryption, username, and password.
5. Click **Send test** on the new connection row to confirm delivery to your administrator email.

As soon as a connection is active, `wp_mail()` routes through it automatically.

== Frequently Asked Questions ==

= Does this work with WooCommerce, Contact Form 7, WPForms, etc.? =

Yes. Anything that uses `wp_mail()` — which is virtually every WordPress plugin that sends email — works without modification.

= Do I need a Google Cloud or Microsoft Azure account? =

No. Authorization is handled by the Emerge OAuth service. You sign in with your normal Google or Microsoft account; no developer setup is required.

= How are my credentials stored? =

All credentials — OAuth tokens for Gmail and Microsoft, plus API keys and SMTP passwords for SendGrid, Mailgun, Amazon SES, Brevo, and Generic SMTP — are encrypted with a key generated locally on your site on first activation and stored as a WordPress option. They never leave your install. For OAuth, the proxy used during sign-in does not persist tokens.

= What happens when an OAuth access token expires? =

Access tokens expire roughly hourly. The plugin refreshes them automatically — both on a schedule and inline if a send happens to coincide with an expiry. You don't need to do anything. API-key connections (SendGrid, Mailgun, SES, Brevo, SMTP) don't expire — the credentials you paste are used as-is until you rotate them yourself.

= What happens if a provider is unreachable? =

The plugin returns an error from its `pre_wp_mail` hook, which causes WordPress to fall back to its default mail delivery (or to the next available connection if you have failover set up). The failure is recorded in the **Last error** column of the settings page and surfaced as an admin notice for 24 hours.

= Can I send from multiple accounts? =

Yes. Connect as many as you like, mixing providers freely — e.g. a SendGrid account, a Gmail mailbox, and an SMTP server as a last-resort fallback. The connection marked **Active** is used first. If a send fails (or the Active connection is in cooldown after a recent error), the plugin automatically falls through to the next available connection. You can switch the Active account at any time from the Connections table.

For high-volume sites, turn on **Throttle connections** in Sender settings to spread sends across all connected accounts instead of always hitting the Active one.

= Can I override the From email? =

Yes. Enter a different address in the **From email** column on the Connections table. It must be a sender that the provider has authorized for this account — a verified send-as alias for Gmail/Microsoft, a verified sender or domain for SendGrid / Mailgun / Amazon SES / Brevo, or whatever the SMTP server allows. Sends from an unauthorized address will be rejected by the provider.

= Can I customize the From display name? =

Yes. Set it in **Sender settings → From name** on the main settings page. Leave it blank to use your site title as the default.

= Can I suppress noisy WordPress notifications I don't care about? =

Yes. Open **Emerge Mail → Email Controls** and tick the notifications you want stopped. Eight WordPress core admin/editor notifications are listed (comment moderation, new comments, password reset notices, new user registrations, and automatic-update results). Suppressed notifications are recorded in **Emerge Mail → Activity Log** so you still have an audit trail. End-user notifications (password resets, account changes, new-user welcome) are intentionally not listed — users need those.

= Does the plugin read my mailbox? =

No. For Gmail and Microsoft, the OAuth scope requested is sufficient only to send mail. The plugin does not call any read endpoints and cannot access existing messages. For SendGrid, Mailgun, SES, Brevo, and SMTP, the plugin only ever posts outbound messages — it does not request inbox reads, contact-list reads, or any other data from the provider.

= Is `wp_mail` still available to other plugins after activation? =

Yes. `wp_mail` continues to function normally. The plugin only changes how the underlying send is performed; it doesn't change any of `wp_mail`'s public behavior.

= Will the plugin work without an active mailbox connection? =

Yes. `wp_mail` falls through to WordPress's default delivery when no connection is available. The plugin never blocks email.

= How do I remove all stored data on uninstall? =

Deleting the plugin from the Plugins screen runs `uninstall.php`, which removes every stored connection, all encrypted tokens, the per-site encryption key, sender/control settings, the suppression-log table, and all scheduled jobs.

== External services ==

To deliver mail, Emerge Mail can communicate with up to eight external services depending on which providers you connect. By installing and using this plugin you agree to use of these services. Nothing is contacted until you connect at least one provider in the plugin settings — every external call below is gated on the corresponding connection existing.

= 1. Emerge OAuth service (emerge.redigit.net) =

**What it is and what it's used for:** A stateless OAuth proxy operated by the plugin author. It exists to broker the OAuth authorization handshake between your WordPress site and Google or Microsoft so the plugin can be authorized to send mail from your mailbox. The proxy holds the OAuth client secrets that Google and Microsoft would otherwise require every site to register individually.

**What data is sent, when:**

* When you click **Connect Gmail** or **Connect Microsoft**, your browser is redirected to `https://emerge.redigit.net/oauth/{provider}/authorize` with a PKCE code challenge and a state token generated by your site. No personal data is sent.
* When the provider redirects back, your site posts the authorization code and PKCE verifier to `https://emerge.redigit.net/oauth/{provider}/token` to receive the access and refresh tokens. The tokens are returned directly to your site and stored locally, encrypted, in `wp_options`. The proxy does not persist them.
* When an access token nears expiry, your site posts the refresh token to `https://emerge.redigit.net/oauth/{provider}/refresh` to obtain a new access token. Again, nothing is persisted on the proxy.
* No email content, recipient lists, or message metadata is ever sent to the proxy.

**Terms and privacy:**

* Terms of Service: <https://emerge.redigit.net/terms-of-service>
* Privacy Policy: <https://emerge.redigit.net/privacy-policy>

= 2. Google APIs (Gmail) =

**What it is and what it's used for:** Used only when you connect a Gmail mailbox. Once authorized, the plugin sends every `wp_mail()` message directly to the Gmail API (`gmail.googleapis.com`) from your WordPress site. Periodically the plugin also calls the Google OAuth userinfo endpoint (`www.googleapis.com/oauth2/v2/userinfo`) to verify the access token is still valid.

**What data is sent, when:**

* On each `wp_mail()` call routed through a Gmail connection: the full outbound message (To/Cc/Bcc, subject, body, attachments, custom headers) is POSTed to `https://gmail.googleapis.com/gmail/v1/users/me/messages/send` as a base64-encoded RFC822 message. This is what's required for the message to reach the recipient.
* On token validation: a Bearer token is sent to `https://www.googleapis.com/oauth2/v2/userinfo`. No message data is included.

**Terms and privacy:**

* Google Terms of Service: <https://policies.google.com/terms>
* Google Privacy Policy: <https://policies.google.com/privacy>
* Google API Services User Data Policy: <https://developers.google.com/terms/api-services-user-data-policy>

= 3. Microsoft Graph (Outlook / Microsoft 365) =

**What it is and what it's used for:** Used only when you connect a Microsoft mailbox. Once authorized, the plugin sends every `wp_mail()` message directly to Microsoft Graph (`graph.microsoft.com`) from your WordPress site. Periodically the plugin also calls Graph's `/me` endpoint to verify the access token is still valid.

**What data is sent, when:**

* On each `wp_mail()` call routed through a Microsoft connection: the full outbound message (To/Cc/Bcc, subject, body, attachments, custom headers) is POSTed to `https://graph.microsoft.com/v1.0/me/sendMail` as a JSON payload. This is what's required for the message to reach the recipient.
* On token validation: a Bearer token is sent to `https://graph.microsoft.com/v1.0/me`. No message data is included.

**Terms and privacy:**

* Microsoft Services Agreement: <https://www.microsoft.com/en/servicesagreement>
* Microsoft Privacy Statement: <https://privacy.microsoft.com/en-us/privacystatement>

= 4. SendGrid (api.sendgrid.com) =

**What it is and what it's used for:** Used only when you add a SendGrid connection. The plugin sends every `wp_mail()` message routed through that connection directly to the SendGrid Mail Send API. The connect-time validation also calls SendGrid's `/v3/user/profile` to confirm the API key works.

**What data is sent, when:**

* On each `wp_mail()` call routed through a SendGrid connection: the outbound message (recipients, subject, body, attachments, custom headers) is POSTed to `https://api.sendgrid.com/v3/mail/send` as a JSON payload.
* On connect: an authenticated GET is sent to `https://api.sendgrid.com/v3/user/profile` to validate the API key. No message data is included.

**Terms and privacy:**

* SendGrid Terms: <https://www.twilio.com/legal/tos>
* SendGrid Privacy Statement: <https://www.twilio.com/legal/privacy>

= 5. Mailgun (api.mailgun.net or api.eu.mailgun.net) =

**What it is and what it's used for:** Used only when you add a Mailgun connection. The plugin sends every `wp_mail()` message routed through that connection directly to the Mailgun Messages API on the region (US or EU) you select at connect time.

**What data is sent, when:**

* On each `wp_mail()` call routed through a Mailgun connection: the outbound message (recipients, subject, body, attachments, custom headers) is POSTed as multipart/form-data to `https://api.mailgun.net/v3/{your-domain}/messages` (or the EU equivalent).
* On connect: an authenticated GET is sent to `/v3/domains/{your-domain}` to validate the API key + domain pairing. No message data is included.

**Terms and privacy:**

* Mailgun Terms of Service: <https://www.mailgun.com/legal/terms/>
* Mailgun Privacy Policy: <https://www.mailgun.com/legal/privacy-policy/>

= 6. Amazon SES (email.{region}.amazonaws.com) =

**What it is and what it's used for:** Used only when you add an Amazon SES connection. The plugin sends every `wp_mail()` message routed through that connection directly to the SES v2 SendEmail API in the AWS region you select at connect time. Requests are signed with AWS Signature V4 using the IAM credentials you provided.

**What data is sent, when:**

* On each `wp_mail()` call routed through a SES connection: the outbound message (recipients, subject, body, custom headers) is POSTed to `https://email.{region}.amazonaws.com/v2/email/outbound-emails` as a JSON payload.
* On connect: a signed GET is sent to `/v2/email/account` in the same region to validate the credentials. No message data is included.

**Terms and privacy:**

* AWS Customer Agreement: <https://aws.amazon.com/agreement/>
* AWS Privacy Notice: <https://aws.amazon.com/privacy/>

= 7. Brevo (api.brevo.com) =

**What it is and what it's used for:** Used only when you add a Brevo connection. The plugin sends every `wp_mail()` message routed through that connection directly to the Brevo Transactional Email API.

**What data is sent, when:**

* On each `wp_mail()` call routed through a Brevo connection: the outbound message (recipients, subject, body, attachments, custom headers) is POSTed to `https://api.brevo.com/v3/smtp/email` as a JSON payload.
* On connect: an authenticated GET is sent to `https://api.brevo.com/v3/account` to validate the API key. No message data is included.

**Terms and privacy:**

* Brevo Terms of Use: <https://www.brevo.com/legal/termsofuse/>
* Brevo Privacy Policy: <https://www.brevo.com/legal/privacypolicy/>

= 8. Your own SMTP server (Generic SMTP) =

**What it is and what it's used for:** Used only when you add a Generic SMTP connection. The plugin opens an SMTP connection to whatever host:port you specify, authenticates with the username and password you provide, and sends `wp_mail()` messages via SMTP. WordPress's bundled PHPMailer library handles the SMTP protocol.

**What data is sent, when:**

* On each `wp_mail()` call routed through this connection: the outbound message (recipients, subject, body, attachments, custom headers) is transmitted over SMTP to your configured host.
* On connect: PHPMailer opens the SMTP connection, runs EHLO + STARTTLS (if enabled), and authenticates — no message is sent.

**Terms and privacy:** Determined by whichever SMTP server you choose to connect to. This plugin does not introduce any third-party SMTP relay; you control the destination.

== Screenshots ==

1. Connections table with Active radio, per-row From email override, last-error column, Send test and Disconnect actions.
2. Connect Gmail / Connect Microsoft buttons with provider logos.
3. Sender settings: From name, Reply-To, Throttle connections, and From header options.
4. Email Controls: opt-in suppression of WordPress core admin/editor self-notifications.
5. Activity Log: suppressed notifications, provider send errors, and recipient bounces in one table with type and severity filters.
6. Site-wide admin notice surfaced when a connection has failed recently.

== Changelog ==

= 1.4.0 =
* New: the Activity Log is now a single log of all mail activity — successful sends through your connections are recorded alongside suppressions, send errors, and bounces.
* New: Provider, Mailbox, and From columns on the log, plus a Provider filter dropdown. Each row is self-contained (provider/mailbox/from are stored on the row, so it survives connection deletion).
* New: `emerge_mail_log_sends` filter (default on) to disable logging of successful sends on high-volume sites.
* New: Reconnect on an Expired API-key connection now re-validates the saved key in place and reactivates it if valid — no need to re-paste an unchanged key when the failure was external (e.g. a provider IP allowlist).
* Changed: "Bulk sending" renamed to "Throttle connections" — when on, sends always rotate across all connected accounts instead of only after a detected burst. Burst detection (and the `emerge_mail_bulk_threshold` / `emerge_mail_bulk_gap_seconds` filters) removed; cooldown connections are still skipped. Existing setting carries over.
* Changed: post-error connection cooldown raised from 10 minutes to 1 hour to better absorb provider quota/rate limits. Still tunable via `emerge_mail_connection_cooldown_seconds`.
* Changed: default log retention lowered from 30 days to 10 days, since successful sends grow the table much faster. Still tunable via `emerge_mail_log_retention_days`.
* Changed: send-error/bounce logging and the bounce classifier are now standard (no longer behind the `EMERGE_MAIL_PRO` gate, which has been removed along with the `emerge_mail_pro_enabled` filter).
* Fix: a successful send (including Send test) now clears a stuck "Expired" status back to "Active" — previously a connection stayed Expired even after the credential was fixed and test mail went through.
* Fix: the dashboard failure notice now names the provider and auth type (e.g. "Gmail (OAuth)") so failures are unambiguous when multiple connections share an email.
* Removed: the "Other admin notices" catch-all in Email Controls (and its `emerge_mail_suppress_other_admin` filter). It could suppress emails sent solely to the admin address — including admin-initiated password resets — so it was too risky to keep. The specific, safe suppression checkboxes remain.

= 1.3.0 =
* New: SendGrid, Mailgun, Amazon SES (Sigv4, no AWS SDK), Brevo, and Generic SMTP (PHPMailer) providers.
* New: per-provider connect forms — credentials are validated against the provider before being persisted.
* Changed: failover and throttle mix-and-match freely across OAuth and API-key providers.
* Changed: bounce classifier extended with provider-specific patterns for all five new providers.
* Changed: token-refresh scheduler skips API-key connections; they're marked `expired` on 401/403.

= 1.2.0 =
* New: Status column on the connections list (Active / Cooldown / Expired).
* New: Reconnect action on expired connections — re-runs OAuth and upserts tokens in place.
* Fix: settings form "link expired" error on sites also running Emerge Campaigns; admin handlers are now page-scoped.
* Changed: form-submit triggers and nonce actions renamed from `emerge_*` to `emerge_mail_*` to avoid collisions.

= 1.1.0 =
* New: "Other admin notices" catch-all suppression in Email Controls (`emerge_mail_suppress_other_admin` filter).
* New: unified Activity Log replaces the Suppression Log (suppressions, send errors, bounces in one table).
* New (pro): synchronous bounce detection and provider send-error logging (`send.bounce.*` / `send.error.*`).
* New: `emerge_mail_event_logged` and `emerge_mail_after_send_attempt` actions; pro gate via `EMERGE_MAIL_PRO`.
* Changed: `wp_mail()` return reflects failure type — transport errors fall back to default mail, recipient bounces return `false` (`emerge_mail_fallback_on_failure` filter).
* Changed: connection-failure admin notice scoped to Dashboard and Plugins screens.
* Schema: `emerge_suppression_log` renamed to `emerge_mail_event_log` with new columns; migrated in place.

= 1.0.0 =
* Initial release.
* OAuth connections to Gmail and all Microsoft account types.
* Multiple mailboxes with active-first routing, automatic failover, and per-connection cooldown.
* Optional throttle that rotates connections, per-connection From override, configurable Sender settings.
* Full `wp_mail` mapping (attachments, Cc/Bcc, multipart, address lists, non-UTF-8, RFC 2047).
* Send-test per connection; Email Controls page; Suppression Log with 30-day pruning.
* Hourly token maintenance (Action Scheduler / WP-Cron); safe fallback to default mail.
* Filter hooks for cooldown, HTTP timeouts, token refresh, MIME output, suppression catalog, and log retention.

== Upgrade Notice ==

= 1.4.0 =
"Bulk sending" becomes "Throttle connections" (rotates across all mailboxes when on). The Activity Log now records successful sends too, with Provider/Mailbox/From columns and a provider filter; default retention drops to 10 days (override via emerge_mail_log_retention_days). The unsafe "Other admin notices" catch-all is removed. Adds a database column migration on upgrade.