to = (array) $emails; return $this; } /** * Set the email sender. * * @param string $email * @param string|null $name * @return $this */ public function from($email, $name = null) { $this->from = $email; $this->fromName = $name ?: get_bloginfo('name'); return $this; } /** * Set one or more CC recipients. * * @param string|array $emails * @return $this */ public function cc($emails) { $this->cc = array_merge($this->cc, (array) $emails); return $this; } /** * Set one or more BCC recipients. * * @param string|array $emails * @return $this */ public function bcc($emails) { $this->bcc = array_merge($this->bcc, (array) $emails); return $this; } /** * Set the email subject. * * @param string $subject * @return $this */ public function subject($subject) { $this->subject = $subject; return $this; } /** * Set the email body (HTML or plain text). * * @param string $body * @return $this */ public function body($body) { $this->body = $body; return $this; } /** * Set the body from a PHP template file with optional data. * * @param string $templatePath * @param array $data * @return $this */ public function view($templatePath, $data = []) { $this->body = App::make(View::class)->make($templatePath, $data); $this->contentType('text/html'); return $this; } /** * Add or override headers. * * @param array|string $headers * @param string|null $value * @return $this */ public function withHeader($headers, $value = null) { if (is_array($headers)) { $this->headers = array_merge($this->headers, $headers); } else { $this->headers[$headers] = $value; } return $this; } /** * Set Content-Type header (default is text/html). * * @param string $contentType * @return $this */ public function contentType($contentType) { $this->contentType = $contentType; return $this; } /** * Attach one or more files. * * @param string|array $paths * @return $this */ public function attach($paths) { $paths = (array) $paths; foreach ($paths as $path) { if (!file_exists($path)) { throw new InvalidArgumentException( "Attachment file not found: {$path}" ); } $this->attachments[] = $path; } return $this; } /** * Prepare headers for wp_mail. * * @return array */ protected function prepareHeaders() { $headers = []; // Note: We removed Content-Type and From from here // because we handle them via filters in send() now. // This prevents duplicate headers which triggers some spam filters. if (!empty($this->cc)) { $headers[] = 'Cc: ' . implode(', ', $this->cc); } if (!empty($this->bcc)) { $headers[] = 'Bcc: ' . implode(', ', $this->bcc); } foreach ($this->headers as $key => $value) { $headers[] = is_int($key) ? $value : "{$key}: {$value}"; } return $headers; } /** * Send the email immediately. * * @return bool */ public function send() { if (empty($this->to)) { throw new InvalidArgumentException( "At least one recipient (to) must be specified." ); } // 1. Set Content Type Filter $contentType = function() { return $this->contentType; }; add_filter('wp_mail_content_type', $contentType); // 2. Set From Filters (if provided) $fromEmailFilter = fn() => $this->from ?: get_bloginfo('admin_email'); $fromNameFilter = fn() => $this->fromName ?: get_bloginfo('name'); if ($this->from) { add_filter('wp_mail_from', $fromEmailFilter); add_filter('wp_mail_from_name', $fromNameFilter); } $to = implode(', ', $this->to); $headers = $this->prepareHeaders(); $result = wp_mail( $to, $this->subject, $this->body, $headers, $this->attachments ); // 3. Clean up filters so we don't affect other emails remove_filter('wp_mail_content_type', $contentType); if ($this->from) { remove_filter('wp_mail_from', $fromEmailFilter); remove_filter('wp_mail_from_name', $fromNameFilter); } return $result; } /** * Static helper to start a new mail instance. * * @return static */ public static function make() { return new static(); } }