From a8ff85042ffbfe94b678fd818c02deb613bfd116 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Fri, 07 Aug 2026 20:42:54 +0000
Subject: [PATCH] =Continuing on with Integrations setup. Working on PostMark now
---
inc/integrations/services/PostMark.php | 914 +++++++++++++++++++++-----------------------------------
1 files changed, 348 insertions(+), 566 deletions(-)
diff --git a/inc/integrations/services/PostMark.php b/inc/integrations/services/PostMark.php
index 4f6f2ee..c8a4956 100644
--- a/inc/integrations/services/PostMark.php
+++ b/inc/integrations/services/PostMark.php
@@ -1,7 +1,12 @@
<?php
namespace JVBase\integrations\services;
+use JVBase\integrations\Admin;
+use JVBase\integrations\Instance;
use JVBase\integrations\Integrations;
+use JVBase\integrations\Requests;
+use WP_Error;
+
if (!defined('ABSPATH')) {
exit;
}
@@ -14,123 +19,69 @@
*/
class PostMark extends Integrations
{
- protected ?string $server_token = null;
- protected string $message_stream = 'outbound';
- protected string $from_email;
- protected string $from_name;
- protected bool $track_open;
- protected bool $track_links;
- protected ?string $lastMessageId = null;
+ use Admin, Instance, Requests;
- protected static array $instances = [];
- public static function getInstance(?int $userID = null):self
- {
- $key = is_null($userID) ? 'base' : $userID;
- if (!array_key_exists($key, self::$instances)) {
- self::$instances[$key] = new static($userID);
- }
- return self::$instances[$key];
- }
- /**
- * Constructor
- */
- protected function __construct(?int $userID = null)
+ protected function __construct()
{
$this->service_name = 'postmark';
$this->title = 'PostMark';
$this->icon = 'envelope-simple';
-
- $this->cacheName = ($userID)? 'gmb_'.$userID : 'postmark';
-
- // PostMark API configuration
- $this->apiBase = 'https://api.postmarkapp.com';
$this->apiVersion = 'v1';
+ $this->baseUrl = 'https://api.postmarkapp.com';
- // Define available endpoints
- $this->apiEndpoints = [
- 'email',
- 'email/batch',
- 'email/withTemplate',
- 'bounces',
- 'deliverystats',
- 'messages/outbound',
- 'messages/outbound/[^/]+',
- 'messages/outbound/opens',
- 'stats/outbound',
- 'stats/outbound/opens',
- 'stats/outbound/bounces',
- 'templates',
- 'templates/[^/]+',
- 'server'
- ];
+ $this->defineFields();
+ $this->defineInstructions();
- // Required credentials
- $this->fields = [
- 'server_token' => [
- 'label' => 'Server API Token',
- 'type' => 'text',
- 'subtype'=> 'password',
- 'placeholder' => 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
- 'hint' => 'Your PostMark Server API Token'
- ],
- 'message_stream' => [
- 'label' => 'Message Stream ID',
- 'type' => 'text',
- 'placeholder' => 'outbound',
- 'default' => 'outbound',
- 'description' => 'Message stream to use (default: outbound)'
- ],
- 'from_email' => [
- 'label' => 'From Email',
- 'type' => 'email',
- 'placeholder' => 'noreply@yourdomain.com',
- 'description' => 'Default sender email address (must be verified in PostMark)'
- ],
- 'from_name' => [
- 'label' => 'From Name',
- 'type' => 'text',
- 'placeholder' => get_bloginfo('name'),
- 'default' => get_bloginfo('name'),
- 'description' => 'Default sender name'
- ],
- 'track_opens' => [
- 'label' => 'Track Opens',
- 'type' => 'select',
- 'options' => [
- 'false' => 'Disabled',
- 'true' => 'Enabled'
- ],
- 'default' => 'false',
- 'description' => 'Track when emails are opened'
- ],
- 'track_links' => [
- 'label' => 'Track Links',
- 'type' => 'select',
- 'options' => [
+ parent::__construct();
+ $this->defineActions();
+ }
+
+ protected function defineFields():void
+ {
+ $token = $this->addField('service_token', 'Server API Token', 'text', 'manage_options');
+ $token->setSubType('password');
+
+ $stream = $this->addField('message_stream', 'Message Stream ID', 'text', 'manage_options');
+ $stream->setDefault('outbound');
+ $stream->setHint('Message stream to use (default: outbound)');
+
+ $from = $this->addField('from_email', 'From Email', 'email', 'manage_options');
+ $from->setPlaceholder('noreply@yourdomain.com');
+ $from->setDefault(get_option('admin_email'));
+
+ $name = $this->addField('from_name', 'From Name', 'text', 'manage_options');
+ $name->setDefault(get_bloginfo('name'));
+ $name->setHint('Default sender name');
+
+ $opens = $this->addField('track_opens', 'Track Opens', 'select', 'manage_options');
+ $opens->setOptions([
+ 'false' => 'Disabled',
+ 'true' => 'Enabled',
+ ]);
+ $opens->setDefault('false');
+ $opens->setHint('Track when emails are opened');
+
+ $links = $this->addField('track_links', 'Track Links', 'select', 'manage_options');
+ $links->setOptions([
'None' => 'Disabled',
'HtmlOnly' => 'HTML Only',
'HtmlAndText' => 'HTML and Text',
- ],
- 'default' => 'None',
- 'description' => 'Track link clicks in emails'
- ]
- ];
-
- parent::__construct();
-
- // Hook into WordPress mail system if integration is healthy
- if ($this->is_healthy && $this->isSetUp()) {
- $this->initializeMailHooks();
+ ]);
+ $links->setDefault('None');
+ $links->setHint('Track link clicks in emails');
}
- }
+ protected function defineInstructions():void
+ {
- /**
- * Initialize WordPress mail hooks
- */
- protected function initializeMailHooks(): void
+ }
+ protected function defineActions():void
+ {
+
+ }
+ protected function initialize(): void
{
// Replace wp_mail with PostMark
- add_filter('wp_mail', [$this, 'sendViaPostMark'], 10, 1);
+ add_filter('wp_mail', [$this, 'wp_mail'], 10, 1);
// Optional: Hook for logging sent emails
add_action('postmark_email_sent', [$this, 'logSentEmail'], 10, 2);
@@ -138,289 +89,224 @@
}
/**
- * Send email via PostMark API
- * Hooks into wp_mail filter
- *
- * @param array $args Email arguments from wp_mail
- * @return array Modified args (or original if sending via PostMark)
+ * Fired on the wp_mail filter
+ * @param array $args Email arguments from wp_mail, including 'to', 'subject', 'message', 'headers', 'attachments'
+ * @return array
*/
- public function sendViaPostMark(array $args): array
- {
- // Extract email components
- $to = $args['to'];
- $subject = $args['subject'];
- $message = $args['message'];
- $headers = $args['headers'] ?? '';
- $attachments = $args['attachments'] ?? [];
+ public function wp_mail(array $args):array
+ {
+ // Extract email components
+ $to = $args['to'];
+ $subject = $args['subject'];
+ $message = $args['message'];
+ $headers = $args['headers']??'';
+ $attachments = $args['attachments'] ?? [];
- // Parse headers
- $parsed_headers = $this->parseMailHeaders($headers);
+ $headers = $this->parseMailHeaders($headers);
- // Determine content type
- $is_html = $parsed_headers['content-type'] === 'text/html' ||
- strpos($message, '<html') !== false ||
- strpos($message, '<body') !== false;
+ $is_html = $headers['content-type'] === 'text/html' || str_contains($message, '<html') || str_contains($message, '<body');
- // Build PostMark payload
- $payload = $this->buildEmailPayload(
- $to,
- $subject,
- $message,
- $is_html,
- $parsed_headers,
- $attachments
- );
-
- // Send via PostMark
- $result = $this->sendEmail($payload);
-
- if ($result === true) {
- error_log('================================ Email sent! ================================');
- // Prevent default wp_mail from sending
- add_filter('pre_wp_mail', '__return_true');
- do_action('postmark_email_sent', $args, $payload);
- } else {
- error_log('=-======================[POSTMARK]Something went wrong... ================================');
- // Log failure but allow fallback to default mail
- do_action('postmark_email_failed', $args, $result);
-
- // Optionally fall back to default wp_mail
- if ($this->shouldFallback()) {
- return $args;
- }
- }
-
- return $args;
- }
-
- /**
- * Build email payload for PostMark API
- */
- protected function buildEmailPayload(
- $to,
- string $subject,
- string $message,
- bool $is_html,
- array $headers,
- array $attachments
- ): array {
- $credentials = $this->getCredentials();
-
- // Handle multiple recipients
- $to_addresses = is_array($to) ? implode(',', $to) : $to;
-
- // Build base payload
- $payload = [
- 'From' => sprintf(
- '%s <%s>',
- $headers['from-name'] ?? $credentials['from_name'],
- $headers['from'] ?? $credentials['from_email']
- ),
- 'To' => $to_addresses,
- 'Subject' => $subject,
- 'TrackOpens' => $credentials['track_opens'] === 'true',
- 'TrackLinks' => $credentials['track_links'],
- 'MessageStream' => $credentials['message_stream'] ?? 'outbound'
- ];
-
- // Set content based on type
- if ($is_html) {
- $payload['HtmlBody'] = $message;
- // Generate text version from HTML
- $payload['TextBody'] = $this->generateTextFromHtml($message);
- } else {
- $payload['TextBody'] = $message;
- }
-
- // Add CC/BCC if present
- if (!empty($headers['cc'])) {
- $payload['Cc'] = is_array($headers['cc']) ?
- implode(',', $headers['cc']) : $headers['cc'];
- }
-
- if (!empty($headers['bcc'])) {
- $payload['Bcc'] = is_array($headers['bcc']) ?
- implode(',', $headers['bcc']) : $headers['bcc'];
- }
-
- // Add Reply-To if present
- if (!empty($headers['reply-to'])) {
- $payload['ReplyTo'] = $headers['reply-to'];
- }
-
- // Handle attachments
- if (!empty($attachments)) {
- $payload['Attachments'] = $this->processAttachments($attachments);
- }
-
- // Add custom headers if needed
- if (!empty($headers['x-headers'])) {
- $payload['Headers'] = $headers['x-headers'];
- }
-
- // Add tags for tracking (optional)
- $payload['Tag'] = $this->determineEmailTag($subject, $headers);
-
- return $payload;
- }
-
- /**
- * Send email via PostMark API
- */
- protected function sendEmail(array $payload): bool|WP_Error
- {
- if (!$this->isSetUp()) {
- return false;
- }
- try {
- $response = $this->postRequest('email', $payload);
- error_log('================================ POSTMARK RESPONSE: ================================'.print_r($response, true));
- if (is_wp_error($response)) {
- return $response;
- }
-
- // Store message ID for tracking
- if (!empty($response['MessageID'])) {
- $this->lastMessageId = $response['MessageID'];
-
- // Cache for potential status checking
- $this->cache->set(
- 'postmark_message_' . $response['MessageID'],
- $payload,
- 3600 // 1 hour
- );
- }
-
- return true;
-
- } catch (\Exception $e) {
- $this->logError('[POSTMARK]', ['method' => 'sendEmail', 'error' => $e->getMessage()]);
- return new WP_Error(
- 'postmark_send_failed',
- $e->getMessage()
+ $response = $this->sendEmail(
+ $to,
+ $subject,
+ $message,
+ $is_html,
+ $headers,
+ $attachments
);
- }
- }
- /**
- * Parse mail headers from various formats
- */
- protected function parseMailHeaders($headers): array
+ if ($response['success']) {
+ error_log('================================ Email sent! ================================');
+ // Prevent default wp_mail from sending
+ add_filter('pre_wp_mail', '__return_true');
+ do_action('postmark_email_sent', $args, $response);
+ } else {
+ error_log('=-======================[POSTMARK]Something went wrong... ================================');
+ // Log failure but allow fallback to default mail
+ do_action('postmark_email_failed', $args, $response);
+ }
+ return $args;
+ }
+
+ protected function parseMailHeaders(string|array $headers):array
+ {
+ $parsed = [
+ 'content-type' => 'text/plain',
+ 'from' => null,
+ 'from-name' => null,
+ 'reply-top' => null,
+ 'cc' => [],
+ 'bcc' => [],
+ 'x-headers' => [],
+ ];
+
+ if (empty($headers)) {
+ return $parsed;
+ }
+
+ if (is_string($headers)) {
+ $headers = explode("\n", $headers);
+ }
+
+ foreach ($headers as $line) {
+ $this->parseHeaderLine($line, $parsed);
+ }
+
+ return $parsed;
+ }
+ protected function parseHeaderLine(string $line, array &$parsed):void
+ {
+ if (empty($line)) {
+ return;
+ }
+ $parts = explode(':', $line, 2);
+ if (count($parts) !== 2) {
+ return;
+ }
+
+ $name = strtolower(trim($parts[0]));
+ $value = trim($parts[1]);
+ switch ($name) {
+ case 'content-type':
+ if (str_contains($value, 'text/html')) {
+ $parsed['content-type'] = $value;
+ }
+ break;
+
+ case 'from':
+ // Parse "Name <email>" format
+ if (preg_match('/^(.+?)\s*<(.+)>$/', $value, $matches)) {
+ $parsed['from-name'] = trim($matches[1], '"\'');
+ $parsed['from'] = $matches[2];
+ } else {
+ $parsed['from'] = $value;
+ }
+ break;
+
+ case 'cc':
+ case 'bcc':
+ $parsed[$name][] = $value;
+ break;
+
+ default:
+ //TODO: Verify we need this. This feels extra clunky
+ //Why not just $parsed['x-headers'][$name] = $value?
+ if (str_contains($name, 'x-')) {
+ $parsed['x-headers'][] = [
+ 'Name' => $name,
+ 'Value' => $value
+ ];
+ } else {
+ $parsed[$name] = $value;
+ }
+ }
+ }
+
+ protected function getRequestHeaders(): array
{
- $parsed = [
- 'content-type' => 'text/plain',
- 'from' => null,
- 'from-name' => null,
- 'reply-to' => null,
- 'cc' => [],
- 'bcc' => [],
- 'x-headers' => []
+ return [
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json',
+ 'X-Postmark-Server-Token' => $this->getServerToken()
];
-
- if (empty($headers)) {
- return $parsed;
- }
-
- // Handle array format
- if (is_array($headers)) {
- foreach ($headers as $header) {
- $this->parseHeaderLine($header, $parsed);
- }
- } else {
- // Handle string format (multiple lines)
- $lines = explode("\n", $headers);
- foreach ($lines as $line) {
- $this->parseHeaderLine($line, $parsed);
- }
- }
-
- return $parsed;
}
- /**
- * Parse individual header line
- */
- protected function parseHeaderLine(string $line, array &$parsed): void
+ protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array
{
- if (empty($line)) {
- return;
+ if (is_wp_error($response) && $response->get_error_message() === 'rate_limit_exceeded') {
+ return $this->response(false, 'Rate limit exceeded');
}
-
- // Split header name and value
- $parts = explode(':', $line, 2);
- if (count($parts) !== 2) {
- return;
- }
-
- $name = strtolower(trim($parts[0]));
- $value = trim($parts[1]);
-
- switch ($name) {
- case 'content-type':
- if (strpos($value, 'text/html') !== false) {
- $parsed['content-type'] = 'text/html';
- }
- break;
-
- case 'from':
- // Parse "Name <email>" format
- if (preg_match('/^(.+?)\s*<(.+)>$/', $value, $matches)) {
- $parsed['from-name'] = trim($matches[1], '"\'');
- $parsed['from'] = $matches[2];
- } else {
- $parsed['from'] = $value;
- }
- break;
-
- case 'reply-to':
- $parsed['reply-to'] = $value;
- break;
-
- case 'cc':
- $parsed['cc'][] = $value;
- break;
-
- case 'bcc':
- $parsed['bcc'][] = $value;
- break;
-
- default:
- // Store custom headers
- if (strpos($name, 'x-') === 0) {
- $parsed['x-headers'][] = [
- 'Name' => $parts[0], // Original case
- 'Value' => $value
- ];
- }
- }
- }
-
- /**
- * Process attachments for PostMark
- */
- protected function processAttachments(array $attachments): array
- {
- $processed = [];
-
- foreach ($attachments as $file) {
- if (!file_exists($file)) {
- continue;
+ $endpoint = str_replace($this->apiBase, '', $endpoint);
+ $error = $response['ErrorCode'] ?? false;
+ if ($error) {
+ switch ($error) {
+ case '0':
+ //Do nothing, no error found
+ break;
+ //Bad or missing API Token
+ case '10':
+ //Multiple errors
+ case '11':
+ //Resource Not found
+ case '12':
+ //Invalid Pagination Key
+ case '13':
+ //Maintenance; PostMark API down for a sec
+ case '100':
+ //Invalid Email Request
+ case '300':
+ //Sender Signature not found
+ case '400':
+ //Sender signature not confirmed
+ case '401':
+ //Invalid JSON
+ case '402':
+ //Invalid request field(s) - {FIELD_NAME}
+ case '403':
+ //Not allowed to send (no more credits in account)
+ case '405':
+ //Inactive recipient. Recipient hard bounces, or filed spam complaint
+ case '406':
+ //JSON required
+ case '409':
+ //Too many requests (500 max)
+ case '410':
+ //Forbidden Attachment type
+ case '411':
+ //Account is pending
+ case '412':
+ //Account may not exist
+ case '413':
+ //Invalid query string parameter
+ case '500':
+ default:
+ $this->logError(
+ 'handleResponse',
+ 'Something went wrong: '.$response['Message']??'',
+ $response
+ );
+ return $this->response(false, 'Something went wrong: '.$response['Message']??'');
}
-
- $processed[] = [
- 'Name' => basename($file),
- 'Content' => base64_encode(file_get_contents($file)),
- 'ContentType' => mime_content_type($file) ?: 'application/octet-stream'
- ];
}
-
- return $processed;
+ return match ($endpoint) {
+ '/email' => $this->handleEmailResponse($response, $method),
+ '/email/bulk' => $this->handleBulkEmailResponse($response, $method),
+ default => $this->response(false, 'Unhandled Response for '. $endpoint . ' endpoint, with method: '. $method),
+ };
}
- /**
- * Generate plain text from HTML content
- */
- protected function generateTextFromHtml(string $html): string
+ /**********************************************************
+ * UTILITY
+ **********************************************************/
+ protected function getServerToken():string|false
+ {
+ return $this->loadCredentials()['server_token']??false;
+ }
+ protected function getMessageStream():string
+ {
+ return $this->loadCredentials()['message_stream']??'outbound';
+ }
+ protected function getFromEmail():string
+ {
+ return $this->loadCredentials()['from_email']??get_option('admin_email');
+ }
+ protected function getFromName():string
+ {
+ return $this->loadCredentials()['from_name']??get_bloginfo('name');
+ }
+ protected function getTrackOpens():string
+ {
+ return $this->loadCredentials()['track_opens']??'false';
+ }
+ protected function getTrackLinks():string
+ {
+ return $this->loadCredentials()['track_links']??'None';
+ }
+
+ /****************************************************************************
+ * HELPERS
+ ****************************************************************************/
+ protected function textFromHtml(string $html):string
{
// Remove HTML tags
$text = strip_tags($html);
@@ -434,228 +320,124 @@
}
/**
- * Determine email tag based on content
+ * @param array $attachments Either an array of file paths, or attachment IDs
+ * @return array
*/
- protected function determineEmailTag(string $subject, array $headers): string
+ protected function processAttachments(array $attachments):array
{
- // Password reset emails
- if (stripos($subject, 'password') !== false && stripos($subject, 'reset') !== false) {
+ $processed = [];
+ foreach ($attachments as $file) {
+ if (is_int($file)) {
+ $file = get_attached_file($file);
+ }
+ if (!$file || !file_exists($file)) {
+ continue;
+ }
+ $processed[] = [
+ 'Name' => basename($file),
+ 'Content' => base64_encode(file_get_contents($file)),
+ 'ContentType' => mime_content_type($file) ?? 'application/octet-stream'
+ ];
+ }
+
+ return $processed;
+ }
+
+ protected function determineEmailTag(string $subject, array $headers):string
+ {
+ if (array_key_exists('email-tag', $headers)) {
+ return $headers['email-tag'];
+ }
+ $tmp = strtolower($subject);
+ if (str_contains($tmp, 'password')) {
return 'password-reset';
}
- // New user registration
- if (stripos($subject, 'welcome') !== false || stripos($subject, 'registration') !== false) {
+ if (str_contains($tmp, 'welcome') || str_contains($tmp, 'registr') || str_contains($tmp, 'register')) {
return 'registration';
}
- // Account notifications
- if (stripos($subject, 'account') !== false) {
+ if (str_contains($tmp, 'account')) {
return 'account';
}
- // Order/transaction emails
- if (stripos($subject, 'order') !== false || stripos($subject, 'receipt') !== false) {
+ if (str_contains($tmp, 'order') || str_contains($tmp, 'receipt')) {
return 'transaction';
}
- // Default
return 'general';
}
- /**
- * Check if should fallback to default mail
- */
- protected function shouldFallback(): bool
+ /****************************************************************************
+ * MAILERS
+ ****************************************************************************/
+ public function sendEmail(string $to, string $subject, string $message, bool $is_html = true, array $headers = [], array $attachments = []):array
{
- // Don't fallback if we've had too many errors
- if ($this->error_stats['consecutive_errors'] >= 3) {
- return false;
+ if (!$this->isSetUp()) {
+ return $this->response(false, 'Not set up');
}
- // Check if fallback is enabled in settings
- $credentials = $this->getCredentials();
- return ($credentials['enable_fallback'] ?? 'true') === 'true';
+ $payload = $this->buildPayload($to, $subject, $message, $is_html, $headers, $attachments);
+
+ $response = $this->postRequest(
+ sprintf('%s/email', $this->apiBase),
+ $payload
+ );
}
+ protected function handleEmailResponse(WP_Error|array $response, string $method):array
+ {
- /**
- * Override makeRequest to add PostMark headers
- */
- protected function getRequestHeaders(): array {
- $credentials = $this->getCredentials();
-
- // Add PostMark specific headers
- return [
- 'Accept' => 'application/json',
- 'Content-Type' => 'application/json',
- 'X-Postmark-Server-Token' => $credentials['server_token'] ?? ''
- ];
- }
-
- /**
- * Get email statistics
- */
- public function getEmailStats(int $days = 30): array
- {
- $cache_key = 'postmark_stats_' . $days;
- $cached = $this->cache->get($cache_key);
-
- if ($cached !== false) {
- return $cached;
}
+ protected function buildPayload(string|array $to, string $subject, string $message, bool $is_html = true, array $headers = [], array $attachments = []):array
+ {
+ $to = is_array($to) ? implode(',',$to) : $to;
- try {
- $stats = $this->getRequest('stats/outbound', [
- 'fromdate' => date('Y-m-d', strtotime("-{$days} days")),
- 'todate' => date('Y-m-d')
- ]);
+ $payload = [
+ 'From' => sprintf(
+ '%s <%s>',
+ $headers['from-name'] ?? $this->getFromName(),
+ $headers['from'] ?? $this->getFromEmail()
+ ),
+ 'To' => $to,
+ 'Subject' => sanitize_text_field($subject),
+ 'TrackOpens' => $this->getTrackOpens() === 'true',
+ 'TrackLinks' => $this->getTrackLinks(),
+ 'MessageStream' => $this->getMessageStream()
+ ];
- if (!is_wp_error($stats)) {
- $this->cache->set($cache_key, $stats, 3600); // Cache for 1 hour
- return $stats;
+ if ($is_html) {
+ $payload['HtmlBody'] = $message;
+ $payload['TextBody'] = $this->textFromHtml($message);
+ } else {
+ $payload['TextBody'] = $message;
}
- } catch (\Exception $e) {
- $this->logError('stats/outbound', ['error' => $e->getMessage()]);
+
+ if (!empty($headers['cc'])) {
+ $payload['Cc'] = is_array($headers['cc']) ?
+ implode(',',$headers['cc']) :
+ $headers['cc'];
+ }
+
+ if (!empty($headers['bcc'])) {
+ $payload['Bcc'] = is_array($headers['bcc']) ?
+ implode(',',$headers['bcc']) :
+ $headers['bcc'];
+ }
+
+ if (!empty($headers['reply-to'])) {
+ $payload['ReplyTo'] = $headers['reply-to'];
+ }
+
+ if (!empty($attachments)) {
+ $payload['Attachments'] = $this->processAttachments($attachments);
+ }
+
+ if (!empty($headers['x-headers'])) {
+ $payload['Headers'] = $headers['x-headers'];
+ }
+
+ $payload['Tag'] = $this->determineEmailTag($subject, $headers);
+
+ return $payload;
}
-
- return [];
- }
-
- /**
- * Get bounce information
- */
- public function getBounces(int $count = 25, int $offset = 0): array
- {
- try {
- return $this->getRequest('bounces', [
- 'count' => $count,
- 'offset' => $offset
- ]);
- } catch (\Exception $e) {
- $this->logError('[POSTMARK]', ['method' => 'getBounces', 'error' => $e->getMessage()]);
- return [];
- }
- }
-
- /**
- * Log sent emails for debugging/tracking
- */
- public function logSentEmail(array $args, array $payload): void
- {
- if (!defined('WP_DEBUG') || !WP_DEBUG) {
- return;
- }
-
- $this->logDebug('Email sent via PostMark', [
- 'to' => $args['to'],
- 'subject' => $args['subject'],
- 'message_id' => $this->lastMessageId ?? 'unknown',
- 'tag' => $payload['Tag'] ?? 'none'
- ]);
- }
-
- /**
- * Log failed email attempts
- */
- public function logFailedEmail(array $args, $error): void
- {
- $error_message = is_wp_error($error) ? $error->get_error_message() : 'Unknown error';
-
- $this->logError('Failed to send email via PostMark', [
- 'to' => $args['to'],
- 'subject' => $args['subject'],
- 'error' => $error_message
- ]);
- }
-
- /**
- * Test email configuration
- */
- public function sendTestEmail(string $to_email): bool|WP_Error
- {
- $payload = [
- 'From' => $this->getCredentials()['from_email'] . ' <' . $this->getCredentials()['from_email'] . '>',
- 'To' => $to_email,
- 'Subject' => 'PostMark Test Email - ' . get_bloginfo('name'),
- 'TextBody' => 'This is a test email from your PostMark integration.',
- 'HtmlBody' => '<p>This is a <strong>test email</strong> from your PostMark integration.</p>',
- 'Tag' => 'test',
- 'TrackOpens' => false,
- 'TrackLinks' => 'None'
- ];
-
- return $this->sendEmail($payload);
- }
-
- /**
- * Render additional options in dashboard
- */
- public function renderAdditionalOptions(): void
- {
- $stats = $this->getEmailStats(7);
-
- ?>
- <div class="postmark-dashboard">
- <h3>Email Statistics (Last 7 Days)</h3>
-
- <?php if (!empty($stats)): ?>
- <div class="stats-grid">
- <div class="stat-item">
- <span class="stat-value"><?= number_format($stats['Sent'] ?? 0) ?></span>
- <span class="stat-label">Emails Sent</span>
- </div>
- <div class="stat-item">
- <span class="stat-value"><?= number_format($stats['Bounced'] ?? 0) ?></span>
- <span class="stat-label">Bounced</span>
- </div>
- <div class="stat-item">
- <span class="stat-value"><?= $stats['Opens'] ?? 0 ?>%</span>
- <span class="stat-label">Open Rate</span>
- </div>
- </div>
- <?php else: ?>
- <p>No statistics available. Check your API configuration.</p>
- <?php endif; ?>
-
- <div class="test-email-section">
- <h4>Send Test Email</h4>
- <form id="postmark-test-email" method="post">
- <input type="email"
- name="test_email"
- placeholder="test@example.com"
- value="<?= esc_attr(wp_get_current_user()->user_email) ?>"
- required>
- <button type="submit" class="button">Send Test</button>
- </form>
- </div>
-
- <?php if ($bounces = $this->getBounces(5)): ?>
- <div class="recent-bounces">
- <h4>Recent Bounces</h4>
- <ul>
- <?php foreach ($bounces as $bounce): ?>
- <li>
- <?= esc_html($bounce['Email']) ?> -
- <?= esc_html($bounce['Type']) ?>
- <small>(<?= esc_html($bounce['BouncedAt']) ?>)</small>
- </li>
- <?php endforeach; ?>
- </ul>
- </div>
- <?php endif; ?>
- </div>
- <?php
- }
-
- protected function initialize(): void
- {
- if (empty($this->credentials)) {
- $this->loadCredentials();
- }
- $this->server_token = (array_key_exists('server_token', $this->credentials)) ? $this->credentials['server_token'] : null;
- $this->from_email = (array_key_exists('from_email', $this->credentials)) ? $this->credentials['from_email'] : get_bloginfo('admin_email');
- $this->from_name = (array_key_exists('from_name', $this->credentials)) ? $this->credentials['from_name'] : get_bloginfo('name');
- $this->message_stream = (array_key_exists('message_stream', $this->credentials)) ? $this->credentials['message_stream'] : 'outbound';
- $this->track_open = (array_key_exists('track_open', $this->credentials)) ? $this->credentials['track_open'] : false;
- $this->track_links = (array_key_exists('track_links', $this->credentials)) ? $this->credentials['track_links'] : false;
- }
}
--
Gitblit v1.10.0