Jake Vanderwerf
2026-08-25 3d25ad7de4fdbb3eba2cde2a62422e6be5d79a0a
inc/integrations/services/PostMark.php
@@ -2,9 +2,10 @@
namespace JVBase\integrations\services;
use JVBase\integrations\Admin;
use JVBase\integrations\Instance;
use JVBase\Instance;
use JVBase\integrations\Integrations;
use JVBase\integrations\Requests;
use JVBase\meta\Meta;
use WP_Error;
if (!defined('ABSPATH')) {
@@ -217,12 +218,27 @@
         return $this->response(false, 'Rate limit exceeded');
      }
      $endpoint = str_replace($this->apiBase, '', $endpoint);
      $checkErrors = $this->checkErrors($response);
      if (!empty($checkErrors)) {
         return $checkErrors;
      }
      return match ($endpoint) {
         '/email' => $this->handleEmailResponse($response, $method),
         '/email/bulk'  => $this->handleBulkEmailResponse($response, $method),
         '/email/batch' => $this->handleBatchEmailResponse($response, $method),
         default => $this->response(false, 'Unhandled Response for '. $endpoint . ' endpoint, with method: '. $method),
      };
   }
      protected function checkErrors(WP_Error|array $response):array|false
      {
      $error = $response['ErrorCode'] ?? false;
      if ($error) {
         switch ($error) {
            case '0':
               //Do nothing, no error found
               break;
                  return false;
            //Bad or missing API Token
            case '10':
            //Multiple errors
@@ -233,6 +249,7 @@
            case '13':
            //Maintenance; PostMark API down for a sec
            case '100':
                  //Maybe schedule a retry?
            //Invalid Email Request
            case '300':
            //Sender Signature not found
@@ -251,12 +268,15 @@
            case '409':
            //Too many requests (500 max)
            case '410':
                  //Maybe schedule a retry?
            //Forbidden Attachment type
            case '411':
            //Account is pending
            case '412':
            //Account may not exist
            case '413':
                  //Some errors in bulk request
               case '422':
            //Invalid query string parameter
            case '500':
            default:
@@ -268,11 +288,7 @@
               return $this->response(false, 'Something went wrong: '.$response['Message']??'');
         }
      }
      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),
      };
         return false;
   }
   /**********************************************************
@@ -379,16 +395,16 @@
      $payload = $this->buildPayload($to, $subject, $message, $is_html, $headers, $attachments);
      $response = $this->postRequest(
      return $this->postRequest(
         sprintf('%s/email', $this->apiBase),
         $payload
      );
   }
      protected function handleEmailResponse(WP_Error|array $response, string $method):array
      {
         return $this->response(true, 'Successfully sent request to PostMark', $response);
      }
      protected function buildPayload(string|array $to, string $subject, string $message, bool $is_html = true, array $headers = [], array $attachments = []):array
      public 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;
@@ -426,6 +442,8 @@
         if (!empty($headers['reply-to'])) {
            $payload['ReplyTo'] = $headers['reply-to'];
         } else {
            $payload['ReplyTo'] = $this->getFromEmail();
         }
         if (!empty($attachments)) {
@@ -440,4 +458,179 @@
         return $payload;
      }
   /**
    * Sends the same message to multiple emails. Can use templates
    * @param array<int> $userIDs
    * @param string $subject
    * @param string $message
    * @param bool $is_html
    * @param array $headers
    * @param array $attachments
    * @return array
    */
   public function sendMessageToMany(array $userIDs, string $subject, string $message, bool $is_html = true, array $headers = [], array $attachments = []):array
   {
      if (!$this->isSetUp()) {
         return $this->response(false, 'Not set up');
      }
      $tags = [];
      $pattern = '/\{\{(.*?)\}\}/';
      if (str_contains($subject, '{{')){
         preg_match_all($pattern, $subject, $tags);
      }
      if (str_contains($message, '{{')) {
         preg_match_all($pattern, $message, $tags);
      }
      $tags = array_unique($tags);
      foreach ($tags as $tag) {
         $t = implode(
            array_map(function($word) {
               return ucfirst($word);
            }, explode('_', $tag)
            ));
         str_replace('{{'.$tag.'}}', '{{'.$t.'}}', $subject);
         str_replace('{{'.$tag.'}}', '{{'.$t.'}}', $message);
      }
      $to = array_map(function ($userID) use ($tags, $subject, $message) {
         $meta = Meta::forUser($userID);
         $email = $meta->get('user_email');
         $out = [];
         if (!empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $out['To'] = $email;
         }
         if (empty($out)) {
            $this->logError('sendMessageToMany', 'User does not have an email address',['user' => $userID]);
            return [];
         }
         $tmp = [];
         foreach ($tags as $tag) {
            $v = $meta->get($tag);
            if (!empty($v)) {
               $tag = implode(
                  array_map(function($word) {
                     return ucfirst($word);
                  }, explode('_', $tag)
                  ));
               $tmp[$tag] = $v;
            }
         }
         if (!empty($tmp)) {
            $out['TemplateModel'] = $tmp;
         }
         return $out;
      }, $userIDs);
      $to = array_filter($to, function($result) {
         return array_key_exists('To', $result) && !empty($result['To']);
      });
      if (empty($to)) {
         $this->logError('sendMessageToMany', 'No users set',['users' => $userIDs]);
      }
      //Onward!
      $payload = [
         'From'   => sprintf(
            '%s <%s>',
            $headers['from-name'] ?? $this->getFromName(),
            $headers['from'] ?? $this->getFromEmail()
         ),
         'Subject'      => sanitize_text_field($subject),
         'TrackOpens'   => $this->getTrackOpens() === 'true',
         'TrackLinks'   => $this->getTrackLinks(),
         'MessageStream'   => 'broadcast'
      ];
      if ($is_html) {
         $payload['HtmlBody'] = $message;
         $payload['TextBody'] = $this->textFromHtml($message);
      } else {
         $payload['TextBody'] = $message;
      }
      if (!empty($headers['reply-to'])) {
         $payload['ReplyTo'] = $headers['reply-to'];
      } else {
         $payload['ReplyTo'] = $this->getFromEmail();
      }
      if (!empty($attachments)) {
         $payload['Attachments'] = $this->processAttachments($attachments);
      }
      if (!empty($headers['x-headers'])) {
         $payload['Headers'] = $headers['x-headers'];
      }
      $payload['Tag'] = $this->determineEmailTag($subject, $headers);
      $payload['Messages'] = $to;
      return $this->postRequest(
         sprintf('%s/email/bulk', $this->apiBase),
         $payload
      );
   }
      protected function handleBulkEmailResponse(WP_Error|array $response, string $method):array
      {
         return $this->response($response['Status']??'' === 'Accepted', 'Successfully sent request to Postmark', $response);
      }
   /**
    * @param array $emails An array of results from buildPayload, ready to batch send to Postmark. Limit of 500 at a time
    * @return array
    */
   public function sendMany(array $emails):array
   {
      if (!$this->isSetUp()) {
         return $this->response(false, 'Not set up');
      }
      $emails = array_filter($emails, function ($email) {
         if (!array_key_exists('To', $email) || !filter_var($email['To'], FILTER_VALIDATE_EMAIL)) {
            $this->logError('sendMany', 'Email does not have a To email address', $email);
            return false;
         }
         //This shouldn't happen, but if it does, we'll just remove it for now.
         if (!array_key_exists('From', $email)) {
            $this->logError('sendMany', 'Email does not have a From email address', $email);
            return false;
         }
         return true;
      });
      if (empty($emails)) {
         $this->logError('sendMany', 'No emails have been sent', $emails);
         return $this->response(false, 'No emails have been sent', $emails);
      }
      return $this->postRequest(
         sprintf(
            '%s/email/batch',
            $this->apiBase
         ),
         $emails
      );
   }
      protected function handleBatchEmailResponse(WP_Error|array $response, string $method):array
      {
         $main = $response[0];
         $checkErrors = $this->checkErrors($main);
         if (!empty($checkErrors)) {
            return $checkErrors;
         }
         if (count($response) > 1) {
            $errors = $response;
            unset($errors[0]);
            foreach ($errors as $error) {
               $this->checkErrors($error);
            }
         }
         return $this->response($main['Message']??'' === 'OK', 'Successfully sent request to Postmark', $response);
      }
}