├── bootstrap.php ├── classes ├── email.php └── email │ ├── driver.php │ └── driver │ ├── mail.php │ ├── mailgun.php │ ├── mandrill.php │ ├── noop.php │ ├── sendmail.php │ └── smtp.php ├── composer.json ├── config └── email.php └── readme.md /bootstrap.php: -------------------------------------------------------------------------------- 1 | __DIR__.'/classes/email.php', 20 | 'Email\\Email_Driver' => __DIR__.'/classes/email/driver.php', 21 | 'Email\\Email_Driver_Mail' => __DIR__.'/classes/email/driver/mail.php', 22 | 'Email\\Email_Driver_Smtp' => __DIR__.'/classes/email/driver/smtp.php', 23 | 'Email\\Email_Driver_Sendmail' => __DIR__.'/classes/email/driver/sendmail.php', 24 | 'Email\\Email_Driver_Noop' => __DIR__.'/classes/email/driver/noop.php', 25 | 'Email\\Email_Driver_Mailgun' => __DIR__.'/classes/email/driver/mailgun.php', 26 | 'Email\\Email_Driver_Mandrill' => __DIR__.'/classes/email/driver/mandrill.php', 27 | 28 | /** 29 | * Email exceptions. 30 | */ 31 | 'Email\\AttachmentNotFoundException' => __DIR__.'/classes/email.php', 32 | 'Email\\InvalidAttachmentsException' => __DIR__.'/classes/email.php', 33 | 'Email\\InvalidEmailStringEncoding' => __DIR__.'/classes/email.php', 34 | 'Email\\EmailSendingFailedException' => __DIR__.'/classes/email.php', 35 | 'Email\\EmailValidationFailedException' => __DIR__.'/classes/email.php', 36 | 37 | /** 38 | * Smtp exceptions 39 | */ 40 | 'Email\\SmtpTimeoutException' => __DIR__.'/classes/email/driver/smtp.php', 41 | 'Email\\SmtpConnectionException' => __DIR__.'/classes/email/driver/smtp.php', 42 | 'Email\\SmtpCommandFailureException' => __DIR__.'/classes/email/driver/smtp.php', 43 | 'Email\\SmtpAuthenticationFailedException' => __DIR__.'/classes/email/driver/smtp.php', 44 | 45 | /** 46 | * Sendmail exceptions 47 | */ 48 | 'Email\\SendmailFailedException' => __DIR__.'/classes/email/driver/sendmail.php', 49 | 'Email\\SendmailConnectionException' => __DIR__.'/classes/email/driver/sendmail.php', 50 | )); 51 | -------------------------------------------------------------------------------- /classes/email.php: -------------------------------------------------------------------------------- 1 | array(), 47 | 'attachment' => array(), 48 | ); 49 | 50 | /** 51 | * Message body 52 | */ 53 | protected $body = ''; 54 | 55 | /** 56 | * Message alt body 57 | */ 58 | protected $alt_body = ''; 59 | 60 | /** 61 | * Message subject 62 | */ 63 | protected $subject = ''; 64 | 65 | /** 66 | * Invalid addresses 67 | */ 68 | protected $invalid_addresses = array(); 69 | 70 | /** 71 | * Message boundaries 72 | */ 73 | protected $boundaries = array(); 74 | 75 | /** 76 | * Message headers 77 | */ 78 | protected $headers = array(); 79 | 80 | /** 81 | * Custom headers 82 | */ 83 | protected $extra_headers = array(); 84 | 85 | /** 86 | * Pipelining enabled? 87 | */ 88 | protected $pipelining = false; 89 | 90 | /** 91 | * Mail type 92 | */ 93 | protected $type = 'plain'; 94 | 95 | /** 96 | * Driver constructor 97 | * 98 | * @param array $config driver config 99 | */ 100 | public function __construct(array $config) 101 | { 102 | $this->config = $config; 103 | } 104 | 105 | /** 106 | * Get a driver config setting. 107 | * 108 | * @param string $key Config key 109 | * @param string $default Default value 110 | * 111 | * @return mixed the config setting value 112 | */ 113 | public function get_config($key, $default = null) 114 | { 115 | return \Arr::get($this->config, $key, $default); 116 | } 117 | 118 | /** 119 | * Set a driver config setting. 120 | * 121 | * @param string $key the config key 122 | * @param mixed $value the new config value 123 | * @return object $this 124 | */ 125 | public function set_config($key, $value) 126 | { 127 | \Arr::set($this->config, $key, $value); 128 | 129 | return $this; 130 | } 131 | 132 | /** 133 | * Enables or disables driver pipelining. 134 | * 135 | * @param bool $pipelining Whether or not to enable pipelining 136 | * 137 | * @return $this 138 | */ 139 | public function pipelining($pipelining = true) 140 | { 141 | $this->pipelining = (bool) $pipelining; 142 | 143 | return $this; 144 | } 145 | 146 | /** 147 | * Gets the body 148 | * 149 | * @return string the message body 150 | */ 151 | public function get_body() 152 | { 153 | return $this->body; 154 | } 155 | 156 | /** 157 | * Sets the body 158 | * 159 | * @param string $body The message body 160 | * 161 | * @return $this 162 | */ 163 | public function body($body) 164 | { 165 | $this->body = (string) $body; 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * Sets the alt body 172 | * 173 | * @param string $alt_body The message alt body 174 | * 175 | * @return $this 176 | */ 177 | public function alt_body($alt_body) 178 | { 179 | $this->alt_body = (string) $alt_body; 180 | 181 | return $this; 182 | } 183 | 184 | /** 185 | * Sets the mail priority 186 | * 187 | * @param string $priority The message priority 188 | * 189 | * @return $this 190 | */ 191 | public function priority($priority) 192 | { 193 | $this->config['priority'] = $priority; 194 | 195 | return $this; 196 | } 197 | 198 | /** 199 | * Sets the html body and optionally a generated alt body. 200 | * 201 | * @param string $html The body html 202 | * @param bool $generate_alt Whether to generate the alt body, will set is html to true 203 | * @param bool $auto_attach Whether to auto attach inline files 204 | * 205 | * @return $this 206 | */ 207 | public function html_body($html, $generate_alt = null, $auto_attach = null) 208 | { 209 | $this->config['is_html'] = true; 210 | 211 | // Check settings 212 | $generate_alt = is_bool($generate_alt) ? $generate_alt : $this->config['generate_alt']; 213 | $auto_attach = is_bool($auto_attach) ? $auto_attach : $this->config['auto_attach']; 214 | $remove_html_comments = isset($this->config['remove_html_comments']) ? (bool) $this->config['remove_html_comments'] : true; 215 | 216 | // Remove html comments 217 | if ($remove_html_comments) 218 | { 219 | $html = preg_replace('//', '', (string) $html); 220 | } 221 | 222 | if ($auto_attach) 223 | { 224 | // Auto attach all images 225 | preg_match_all("/(src|background)=\"(.*)\"/Ui", $html, $images); 226 | if ( ! empty($images[2])) 227 | { 228 | foreach ($images[2] as $i => $image_url) 229 | { 230 | // convert inline images to cid attachments 231 | if (preg_match('/^data:image\/(.*);base64,\s(.*)$/', $image_url, $image)) 232 | { 233 | // create a temp image for the attachmment 234 | $file = strtolower(tempnam(sys_get_temp_dir(), 'inline-').'.'.$image[1]); 235 | file_put_contents($file, base64_decode($image[2])); 236 | 237 | // attach the temp file 238 | $cid = 'cid:'.md5($file); 239 | $this->attach($file, true, $cid); 240 | 241 | // and remove it 242 | unlink($file); 243 | $html = preg_replace("/".$images[1][$i]."=\"".preg_quote($image_url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $html); 244 | } 245 | // Don't attach absolute urls 246 | elseif ( ! preg_match('/(^http\:\/\/|^https\:\/\/|^\/\/|^cid\:|^data\:|^#)/Ui', $image_url)) 247 | { 248 | $cid = 'cid:'.md5(pathinfo($image_url, PATHINFO_BASENAME)); 249 | if ( ! isset($this->attachments['inline'][$cid])) 250 | { 251 | $this->attach($image_url, true, $cid); 252 | } 253 | $html = preg_replace("/".$images[1][$i]."=\"".preg_quote($image_url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $html); 254 | } 255 | 256 | // Deal with relative protocol URI's if needed 257 | elseif ($scheme = $this->get_config('relative_protocol_replacement', false) and strpos($image_url, '//') === 0) 258 | { 259 | $html = preg_replace("/".$images[1][$i]."=\"".preg_quote($image_url, '/')."\"/Ui", $images[1][$i]."=\"".$scheme.substr($image_url, 2)."\"", $html); 260 | } 261 | } 262 | } 263 | } 264 | 265 | $this->body = $html; 266 | 267 | $generate_alt and $this->alt_body = static::generate_alt($html, $this->config['wordwrap'], $this->config['newline']); 268 | 269 | return $this; 270 | } 271 | 272 | /** 273 | * Gets the message subject 274 | * 275 | * @return string the message subject 276 | */ 277 | public function get_subject() 278 | { 279 | return $this->subject; 280 | } 281 | 282 | /** 283 | * Sets the message subject 284 | * 285 | * @param string $subject The message subject 286 | * 287 | * @return $this 288 | */ 289 | public function subject($subject) 290 | { 291 | if ($this->config['encode_headers']) 292 | { 293 | $subject = $this->encode_mimeheader((string) $subject); 294 | } 295 | $this->subject = (string) $subject; 296 | 297 | return $this; 298 | } 299 | 300 | /** 301 | * Gets from address and name 302 | * 303 | * @return array from address and name 304 | */ 305 | public function get_from() 306 | { 307 | return $this->config['from']; 308 | } 309 | 310 | /** 311 | * Sets the from address and name 312 | * 313 | * @param string $email The from email address 314 | * @param bool|string $name The optional from name 315 | * 316 | * @return $this 317 | */ 318 | public function from($email, $name = false) 319 | { 320 | if (is_array($email) and isset($email['email']) and isset($email['name'])) 321 | { 322 | $this->config['from'] = $email; 323 | } 324 | else 325 | { 326 | $this->config['from']['email'] = (string) $email; 327 | $this->config['from']['name'] = (is_string($name)) ? $name : false; 328 | 329 | } 330 | 331 | if ($this->config['encode_headers'] and ! empty($this->config['from']['name'])) 332 | { 333 | $this->config['from']['name'] = $this->encode_mimeheader((string) $this->config['from']['name']); 334 | } 335 | 336 | return $this; 337 | } 338 | 339 | /** 340 | * Gets to recipients list. 341 | * 342 | * @return array to recipients list 343 | */ 344 | public function get_to() 345 | { 346 | return $this->to; 347 | } 348 | 349 | /** 350 | * Add to the to recipients list. 351 | * 352 | * @param string|array $email Email address or list of email addresses, array(email => name, email) 353 | * @param string|bool $name Recipient name, false, null or empty for no name 354 | * 355 | * @return $this 356 | */ 357 | public function to($email, $name = false) 358 | { 359 | static::add_to_list('to', $email, $name); 360 | 361 | return $this; 362 | } 363 | 364 | /** 365 | * Gets to cc recipients list. 366 | * 367 | * @return array to cc recipients list 368 | */ 369 | public function get_cc() 370 | { 371 | return $this->cc; 372 | } 373 | 374 | /** 375 | * Add to the cc recipients list. 376 | * 377 | * @param string|array $email Email address or list of email addresses, array(email => name, email) 378 | * @param string|bool $name Recipient name, false, null or empty for no name 379 | * 380 | * @return $this 381 | */ 382 | public function cc($email, $name = false) 383 | { 384 | static::add_to_list('cc', $email, $name); 385 | 386 | return $this; 387 | } 388 | 389 | /** 390 | * Gets to bcc recipients list. 391 | * 392 | * @return array to bcc recipients list 393 | */ 394 | public function get_bcc() 395 | { 396 | return $this->bcc; 397 | } 398 | 399 | /** 400 | * Add to the bcc recipients list. 401 | * 402 | * @param string|array $email Email address or list of email addresses, array(email => name, email) 403 | * @param string|bool $name Recipient name, false, null or empty for no name 404 | * 405 | * @return $this 406 | */ 407 | public function bcc($email, $name = false) 408 | { 409 | static::add_to_list('bcc', $email, $name); 410 | 411 | return $this; 412 | } 413 | 414 | /** 415 | * Gets to 'reply to' recipients list. 416 | * 417 | * @return array to 'reply to' recipients list 418 | */ 419 | public function get_reply_to() 420 | { 421 | return $this->reply_to; 422 | } 423 | 424 | /** 425 | * Add to the 'reply to' list. 426 | * 427 | * @param string|array $email Email address or list of email addresses, array(email => name, email) 428 | * @param string|bool $name The name, false, null or empty for no name 429 | * 430 | * @return $this 431 | */ 432 | public function reply_to($email, $name = false) 433 | { 434 | static::add_to_list('reply_to', $email, $name); 435 | 436 | return $this; 437 | } 438 | 439 | /** 440 | * Sets the return-path address 441 | * 442 | * @param string $email The return-path email address 443 | * 444 | * @return $this 445 | */ 446 | public function return_path($email) 447 | { 448 | $this->config['return_path'] = (string) $email; 449 | 450 | return $this; 451 | } 452 | 453 | /** 454 | * Add to a recipients list. 455 | * 456 | * @param string $list List to add to (to, cc, bcc) 457 | * @param string|array $email Email address or list of email addresses, array(email => name, email) 458 | * @param string|bool $name Recipient name, false, null or empty for no name 459 | * 460 | * @return void 461 | */ 462 | protected function add_to_list($list, $email, $name = false) 463 | { 464 | if ( ! is_array($email)) 465 | { 466 | $email = (is_string($name)) ? array($email => $name) : array($email); 467 | } 468 | 469 | if (isset($email['name']) and isset($email['email'])) 470 | { 471 | $this->{$list}[$email['email']] = $email; 472 | } 473 | else 474 | { 475 | foreach ($email as $_email => $name) 476 | { 477 | if (is_numeric($_email)) 478 | { 479 | $_email = $name; 480 | $name = false; 481 | } 482 | 483 | if ($this->config['encode_headers'] and ! empty($name)) 484 | { 485 | $name = $this->encode_mimeheader($name); 486 | } 487 | 488 | $this->{$list}[$_email] = array( 489 | 'name' => $name, 490 | 'email' => $_email, 491 | ); 492 | } 493 | } 494 | } 495 | 496 | /** 497 | * Clear the a recipient list. 498 | * 499 | * @param string|array $list List or array of lists 500 | * 501 | * @return void 502 | */ 503 | protected function clear_list($list) 504 | { 505 | is_array($list) or $list = array($list); 506 | 507 | foreach ($list as $_list) 508 | { 509 | $this->{$_list} = array(); 510 | } 511 | } 512 | 513 | /** 514 | * Clear all recipient lists. 515 | * 516 | * @return $this 517 | */ 518 | public function clear_recipients() 519 | { 520 | static::clear_list(array('to', 'cc', 'bcc')); 521 | 522 | return $this; 523 | } 524 | 525 | /** 526 | * Clear all address lists. 527 | * 528 | * @return $this 529 | */ 530 | public function clear_addresses() 531 | { 532 | static::clear_list(array('to', 'cc', 'bcc', 'reply_to')); 533 | 534 | return $this; 535 | } 536 | 537 | /** 538 | * Clear the 'to' recipient list. 539 | * 540 | * @return $this 541 | */ 542 | public function clear_to() 543 | { 544 | static::clear_list('to'); 545 | 546 | return $this; 547 | } 548 | 549 | /** 550 | * Clear the 'cc' recipient list. 551 | * 552 | * @return $this 553 | */ 554 | public function clear_cc() 555 | { 556 | static::clear_list('cc'); 557 | 558 | return $this; 559 | } 560 | 561 | /** 562 | * Clear the 'bcc' recipient list. 563 | * 564 | * @return $this 565 | */ 566 | public function clear_bcc() 567 | { 568 | static::clear_list('bcc'); 569 | 570 | return $this; 571 | } 572 | 573 | /** 574 | * Clear the 'reply to' recipient list. 575 | * 576 | * @return $this 577 | */ 578 | public function clear_reply_to() 579 | { 580 | static::clear_list('reply_to'); 581 | 582 | return $this; 583 | } 584 | 585 | /** 586 | * Sets custom headers. 587 | * 588 | * @param string|array $header Header type or array of headers 589 | * @param string $value Header value 590 | * @return $this 591 | */ 592 | public function header($header, $value = null) 593 | { 594 | if(is_array($header)) 595 | { 596 | foreach($header as $_header => $_value) 597 | { 598 | empty($_value) or $this->extra_headers[$_header] = $_value; 599 | } 600 | } 601 | else 602 | { 603 | empty($value) or $this->extra_headers[$header] = $value; 604 | } 605 | 606 | return $this; 607 | } 608 | 609 | /** 610 | * Attaches a file to the email. This method will search for the file in the attachment paths set (config/email.php) in the attach_paths array 611 | * 612 | * @param string $file The file to attach 613 | * @param bool $inline Whether to include the file inline 614 | * @param string $cid The content identifier. Used when attaching inline images 615 | * @param string $mime The file's mime-type 616 | * @param string $name The attachment's name 617 | * 618 | * @throws \InvalidAttachmentsException Could not read attachment or attachment is empty 619 | * 620 | * @return $this 621 | */ 622 | public function attach($file, $inline = false, $cid = null, $mime = null, $name = null) 623 | { 624 | $file = (array) $file; 625 | 626 | // Ensure the attachment name 627 | if ( ! isset($file[1])) 628 | { 629 | $name or $name = pathinfo($file[0], PATHINFO_BASENAME); 630 | $file[1] = $name; 631 | } 632 | 633 | // Find the attachment. 634 | $file[0] = $this->find_attachment($file[0]); 635 | 636 | if (($contents = file_get_contents($file[0])) === false or empty($contents)) 637 | { 638 | throw new \InvalidAttachmentsException('Could not read attachment or attachment is empty: '.$file[0]); 639 | } 640 | 641 | $disp = ($inline) ? 'inline' : 'attachment'; 642 | 643 | $cid = empty($cid) ? 'cid:'.md5($file[1]) : trim($cid); 644 | $cid = strpos($cid, 'cid:') === 0 ? $cid : 'cid:'.$cid; 645 | 646 | // Fetch the file mime type. 647 | $mime or $mime = static::attachment_mime($file[0]); 648 | 649 | $this->attachments[$disp][$cid] = array( 650 | 'file' => $file, 651 | 'contents' => chunk_split(base64_encode($contents), 76, $this->config['newline']), 652 | 'mime' => $mime, 653 | 'disp' => $disp, 654 | 'cid' => $cid, 655 | ); 656 | 657 | return $this; 658 | } 659 | 660 | /** 661 | * Finds an attachment. 662 | * 663 | * @param $file 664 | * 665 | * @throws \AttachmentNotFoundException Email attachment not found 666 | * 667 | * @return string path of the first found attachment 668 | */ 669 | protected function find_attachment($file) 670 | { 671 | foreach($this->get_config('attach_paths') as $path) 672 | { 673 | if(is_file($path.$file)) 674 | { 675 | return $path.$file; 676 | } 677 | } 678 | 679 | // No file found? 680 | throw new \AttachmentNotFoundException('Email attachment not found: '.$file); 681 | } 682 | 683 | /** 684 | * Attach a file using string input 685 | * 686 | * @param string $contents File contents 687 | * @param string $filename The files name 688 | * @param string $cid The content identifier. Used when attaching inline images 689 | * @param bool $inline Whether it's an inline attachment 690 | * @param string $mime The file's mime-type 691 | * 692 | * @return $this 693 | */ 694 | public function string_attach($contents, $filename, $cid = null, $inline = false, $mime = null) 695 | { 696 | $disp = ($inline) ? 'inline' : 'attachment'; 697 | $cid = empty($cid) ? 'cid:'.md5($filename) : trim($cid); 698 | $cid = strpos($cid, 'cid:') === 0 ? $cid : 'cid:'.$cid; 699 | $mime or $mime = static::attachment_mime($filename); 700 | 701 | $this->attachments[$disp][$cid] = array( 702 | 'file' => array(0 => $filename, 1 => pathinfo($filename, PATHINFO_BASENAME)), 703 | 'contents' => static::encode_string($contents, 'base64', $this->config['newline']), 704 | 'mime' => $mime, 705 | 'disp' => $disp, 706 | 'cid' => $cid, 707 | ); 708 | 709 | return $this; 710 | } 711 | 712 | /** 713 | * Clear the attachments list. 714 | * 715 | * @return $this 716 | */ 717 | public function clear_attachments() 718 | { 719 | $this->attachments = array( 720 | 'inline' => array(), 721 | 'attachment' => array(), 722 | ); 723 | 724 | return $this; 725 | } 726 | 727 | /** 728 | * Get the mimetype for an attachment 729 | * 730 | * @param string $file The path to the attachment 731 | * 732 | * @return $this 733 | */ 734 | protected static function attachment_mime($file) 735 | { 736 | static $mimes = false; 737 | 738 | if ( ! $mimes) 739 | { 740 | $mimes = \Config::load('mimes'); 741 | } 742 | 743 | $ext = pathinfo($file, PATHINFO_EXTENSION); 744 | 745 | $mime = \Arr::get($mimes, $ext, 'application/octet-stream'); 746 | is_array($mime) and $mime = reset($mime); 747 | 748 | return $mime; 749 | } 750 | 751 | /** 752 | * Validates all the email addresses. 753 | * 754 | * @return bool|array True if all are valid or an array of recipients which failed validation. 755 | */ 756 | protected function validate_addresses() 757 | { 758 | $failed = array(); 759 | 760 | foreach (array('to', 'cc', 'bcc') as $list) 761 | { 762 | foreach ($this->{$list} as $recipient) 763 | { 764 | if ( ! filter_var($recipient['email'], FILTER_VALIDATE_EMAIL)) 765 | { 766 | $failed[$list][] = $recipient; 767 | } 768 | } 769 | } 770 | 771 | if (count($failed) === 0) 772 | { 773 | return true; 774 | } 775 | 776 | return $failed; 777 | } 778 | 779 | /** 780 | * Sets unique message boundaries 781 | */ 782 | protected function set_boundaries() 783 | { 784 | $uniq_id = md5(uniqid(microtime(true))); 785 | 786 | // Message part boundary, (separates message and attachments). 787 | $this->boundaries[0] = 'B1_'.$uniq_id; 788 | 789 | // Message body boundary (separates message, alt message) 790 | $this->boundaries[1] = 'B2_'.$uniq_id; 791 | 792 | $this->boundaries[2] = 'B3_'.$uniq_id; 793 | } 794 | 795 | /** 796 | * Initiates the sending process. 797 | * 798 | * @param bool Whether to validate the addresses, falls back to config setting 799 | * 800 | * @throws \EmailValidationFailedException One or more email addresses did not pass validation 801 | * @throws \FuelException Cannot send without from address/Cannot send without recipients 802 | * 803 | * @return bool 804 | */ 805 | public function send($validate = null) 806 | { 807 | if (empty($this->to) and empty($this->cc) and empty($this->bcc)) 808 | { 809 | throw new \FuelException('Cannot send email without recipients.'); 810 | } 811 | 812 | if (($from = $this->config['from']['email']) === false or empty($from)) 813 | { 814 | throw new \FuelException('Cannot send without from address.'); 815 | } 816 | 817 | // Check which validation bool to use 818 | is_bool($validate) or $validate = $this->config['validate']; 819 | 820 | // Validate the email addresses if specified 821 | if ($validate and ($failed = $this->validate_addresses()) !== true) 822 | { 823 | $this->invalid_addresses = $failed; 824 | 825 | $error_str = ''; 826 | foreach($failed as $_list => $_contents) 827 | { 828 | $error_str .= $_list.': '.htmlentities(static::format_addresses($_contents)).'.'.PHP_EOL; 829 | } 830 | 831 | throw new \EmailValidationFailedException('One or more email addresses did not pass validation: '.$error_str); 832 | } 833 | 834 | // Reset the headers 835 | $this->headers = array(); 836 | 837 | // Set the email boundaries 838 | $this->set_boundaries(); 839 | 840 | // Set RFC 822 formatted date 841 | $this->set_header('Date', date('r')); 842 | 843 | // Set return path 844 | if ($this->config['return_path'] !== false) 845 | { 846 | $this->set_header('Return-Path', $this->config['return_path']); 847 | } 848 | else 849 | { 850 | $this->set_header('Return-Path', $this->config['from']['email']); 851 | } 852 | 853 | if ( ! empty($this->config['force_to'])) 854 | { 855 | foreach (array('to', 'cc', 'bcc', 'reply_to') as $list) 856 | { 857 | foreach ($this->{$list} as $index => $value) 858 | { 859 | $this->{$list}[$index]['email'] = $this->config['force_to']; 860 | } 861 | } 862 | $this->set_header('Return-Path', $this->config['force_to']); 863 | 864 | if (empty($this->reply_to)) 865 | { 866 | $this->reply_to($this->config['force_to']); 867 | } 868 | } 869 | 870 | if (($this instanceof Email_Driver_Mail) !== true) 871 | { 872 | if ( ! empty($this->to)) 873 | { 874 | // Set from 875 | $this->set_header('To', static::format_addresses($this->to)); 876 | } 877 | 878 | // Set subject 879 | $this->set_header('Subject', $this->subject); 880 | } 881 | 882 | $this->set_header('From', static::format_addresses(array($this->config['from']))); 883 | 884 | foreach (array('cc' => 'Cc', 'bcc' => 'Bcc', 'reply_to' => 'Reply-To') as $list => $header) 885 | { 886 | if (count($this->{$list}) > 0) 887 | { 888 | $this->set_header($header, static::format_addresses($this->{$list})); 889 | } 890 | } 891 | 892 | // Set message id 893 | $this->set_header('Message-ID', $this->get_message_id()); 894 | 895 | // Set mime version 896 | $this->set_header('MIME-Version', '1.0'); 897 | 898 | // Set priority 899 | $this->set_header('X-Priority', $this->config['priority']); 900 | 901 | // Set mailer useragent 902 | $this->set_header('X-Mailer', $this->config['useragent']); 903 | 904 | $newline = $this->config['newline']; 905 | 906 | $this->type = $this->get_mail_type(); 907 | 908 | $encoding = $this->config['encoding']; 909 | $charset = $this->config['charset']; 910 | 911 | if ($this->type !== 'plain' and $this->type !== 'html') 912 | { 913 | $this->set_header('Content-Type', $this->get_content_type($this->type, $newline."\tboundary=\"".$this->boundaries[0].'"')); 914 | } 915 | else 916 | { 917 | $this->set_header('Content-Transfer-Encoding', $encoding); 918 | $this->set_header('Content-Type', 'text/'.$this->type.'; charset="'.$this->config['charset'].'"'); 919 | } 920 | 921 | // Encode messages 922 | $this->body = static::encode_string($this->body, $encoding, $newline); 923 | $this->alt_body = static::encode_string($this->alt_body, $encoding, $newline); 924 | 925 | // Set wordwrapping 926 | $wrapping = $this->config['wordwrap']; 927 | $qp_mode = $encoding === 'quoted-printable'; 928 | 929 | $is_html = (stripos($this->type, 'html') !== false); 930 | 931 | // Don't wrap the text when using quoted-printable 932 | if ($wrapping and ! $qp_mode) 933 | { 934 | $this->body = static::wrap_text($this->body, $wrapping, $newline, $is_html); 935 | $this->alt_body = static::wrap_text($this->alt_body, $wrapping, $newline, false); 936 | } 937 | 938 | // Send 939 | $this->_send(); 940 | 941 | return true; 942 | } 943 | 944 | /** 945 | * Get the invalid addresses 946 | * 947 | * @return array An array of invalid email addresses 948 | */ 949 | public function get_invalid_addresses() 950 | { 951 | return $this->invalid_addresses; 952 | } 953 | 954 | /** 955 | * Sets the message headers 956 | * 957 | * @param string $header The header type 958 | * @param string $value The header value 959 | */ 960 | protected function set_header($header, $value) 961 | { 962 | empty($value) or $this->headers[$header] = $value; 963 | } 964 | 965 | /** 966 | * Gets the header 967 | * 968 | * @param string $header The header name. Will return all headers, if not specified 969 | * @param bool $formatted Adds newline as suffix and colon as prefix, if true 970 | * 971 | * @return string|array Mail header or array of headers 972 | */ 973 | protected function get_header($header = null, $formatted = true) 974 | { 975 | if ($header === null) 976 | { 977 | return $this->headers; 978 | } 979 | 980 | if (array_key_exists($header, $this->headers)) 981 | { 982 | $prefix = ($formatted) ? $header.': ' : ''; 983 | $suffix = ($formatted) ? $this->config['newline'] : ''; 984 | return $prefix.$this->headers[$header].$suffix; 985 | } 986 | 987 | return ''; 988 | } 989 | 990 | /** 991 | * Encodes a mimeheader. 992 | * 993 | * @param string $header Header to encode 994 | * 995 | * @return string Mimeheader encoded string 996 | */ 997 | protected function encode_mimeheader($header) 998 | { 999 | // we need mbstring for this 1000 | if ( ! MBSTRING) 1001 | { 1002 | throw new \RuntimeException('Email requires the multibyte ("mbstring") package to be installed!'); 1003 | } 1004 | 1005 | // determine the transfer encoding to be used 1006 | $transfer_encoding = ($this->config['encoding'] === 'quoted-printable') ? 'Q' : 'B' ; 1007 | 1008 | // encode 1009 | $header = mb_encode_mimeheader($header, $this->config['charset'], $transfer_encoding, $this->config['newline']); 1010 | 1011 | // and return it 1012 | return $header; 1013 | } 1014 | 1015 | /** 1016 | * Get the attachment headers 1017 | * 1018 | */ 1019 | protected function get_attachment_headers($type, $boundary) 1020 | { 1021 | $return = ''; 1022 | 1023 | $newline = $this->config['newline']; 1024 | 1025 | foreach ($this->attachments[$type] as $attachment) 1026 | { 1027 | $return .= '--'.$boundary.$newline; 1028 | $return .= 'Content-Type: '.$attachment['mime'].'; name="'.$attachment['file'][1].'"'.$newline; 1029 | $return .= 'Content-Transfer-Encoding: base64'.$newline; 1030 | $type === 'inline' and $return .= 'Content-ID: <'.substr($attachment['cid'], 4).'>'.$newline; 1031 | $return .= 'Content-Disposition: '.$type.'; filename="'.$attachment['file'][1].'"'.$newline.$newline; 1032 | $return .= $attachment['contents'].$newline.$newline; 1033 | } 1034 | 1035 | return $return; 1036 | } 1037 | 1038 | /** 1039 | * Get a unique message id 1040 | * 1041 | * @return string The message id 1042 | */ 1043 | protected function get_message_id() 1044 | { 1045 | $from = $this->config['from']['email']; 1046 | return "<".uniqid('').strstr($from, '@').">"; 1047 | } 1048 | 1049 | /** 1050 | * Returns the mail's type 1051 | * 1052 | * @return string Mail type 1053 | */ 1054 | protected function get_mail_type() 1055 | { 1056 | $return = $this->config['is_html'] ? 'html' : 'plain' ; 1057 | $alt = trim($this->alt_body); 1058 | $return .= ($this->config['is_html'] and ! empty($alt)) ? '_alt' : ''; 1059 | $return .= ($this->config['is_html'] and count($this->attachments['inline'])) ? '_inline' : ''; 1060 | $return .= (count($this->attachments['attachment'])) ? '_attach' : ''; 1061 | return $return; 1062 | } 1063 | 1064 | /** 1065 | * Returns the content type 1066 | * 1067 | * @param string $mail_type Type of email (plain, html, html_inline, etc…) 1068 | * @param $boundary 1069 | * 1070 | * @throws \FuelException Invalid content-type 1071 | * 1072 | * @return string Mail content type 1073 | */ 1074 | protected function get_content_type($mail_type, $boundary) 1075 | { 1076 | $related = $this->config['force_mixed'] ? 'multipart/mixed; ' : 'multipart/related; '; 1077 | 1078 | switch ($mail_type) 1079 | { 1080 | case 'plain': 1081 | return 'text/plain'; 1082 | case 'plain_attach': 1083 | case 'html_attach': 1084 | return $related.$boundary; 1085 | case 'html': 1086 | return 'text/html'; 1087 | case 'html_alt_attach': 1088 | case 'html_inline_attach': 1089 | case 'html_alt_inline_attach': 1090 | return 'multipart/mixed; '.$boundary; 1091 | case 'html_alt_inline': 1092 | case 'html_alt': 1093 | case 'html_inline': 1094 | return 'multipart/alternative; '.$boundary; 1095 | default: 1096 | throw new \FuelException('Invalid content-type'.$mail_type); 1097 | } 1098 | } 1099 | 1100 | /** 1101 | * Builds the headers and body 1102 | * 1103 | * @param bool $no_bcc Whether to exclude Bcc headers. 1104 | * 1105 | * @return array An array containing the headers and the body 1106 | */ 1107 | protected function build_message($no_bcc = false) 1108 | { 1109 | $newline = $this->config['newline']; 1110 | $charset = $this->config['charset']; 1111 | $encoding = $this->config['encoding']; 1112 | 1113 | $headers = ''; 1114 | $parts = array('Date', 'Return-Path', 'From', 'To', 'Cc', 'Bcc', 'Reply-To', 'Subject', 'Message-ID', 'X-Priority', 'X-Mailer', 'MIME-Version', 'Content-Type'); 1115 | $no_bcc and array_splice($parts, 5, 1); 1116 | 1117 | foreach ($parts as $part) 1118 | { 1119 | $headers .= $this->get_header($part); 1120 | } 1121 | 1122 | foreach ($this->extra_headers as $header => $value) 1123 | { 1124 | $headers .= $header.': '.$value.$newline; 1125 | } 1126 | 1127 | $headers .= $newline; 1128 | 1129 | $body = ''; 1130 | 1131 | if ($this->type === 'plain' or $this->type === 'html') 1132 | { 1133 | $body = $this->body; 1134 | } 1135 | else 1136 | { 1137 | switch ($this->type) 1138 | { 1139 | case 'html_alt': 1140 | $body .= '--'.$this->boundaries[0].$newline; 1141 | $body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline; 1142 | $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline; 1143 | $body .= $this->alt_body.$newline.$newline; 1144 | $body .= '--'.$this->boundaries[0].$newline; 1145 | $body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline; 1146 | $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline; 1147 | $body .= $this->body.$newline.$newline; 1148 | $body .= '--'.$this->boundaries[0].'--'; 1149 | break; 1150 | case 'plain_attach': 1151 | case 'html_attach': 1152 | case 'html_inline': 1153 | $body .= '--'.$this->boundaries[0].$newline; 1154 | $text_type = (stripos($this->type, 'html') !== false) ? 'html' : 'plain'; 1155 | $body .= 'Content-Type: text/'.$text_type.'; charset="'.$charset.'"'.$newline; 1156 | $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline; 1157 | $body .= $this->body.$newline.$newline; 1158 | $attach_type = (stripos($this->type, 'attach') !== false) ? 'attachment' : 'inline'; 1159 | $body .= $this->get_attachment_headers($attach_type, $this->boundaries[0]); 1160 | $body .= '--'.$this->boundaries[0].'--'; 1161 | break; 1162 | case 'html_alt_inline': 1163 | $body .= '--'.$this->boundaries[0].$newline; 1164 | $body .= 'Content-Type: text/plain'.'; charset="'.$charset.'"'.$newline; 1165 | $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline; 1166 | $body .= $this->alt_body.$newline.$newline; 1167 | $body .= '--'.$this->boundaries[0].$newline; 1168 | $body .= 'Content-Type: multipart/related;'.$newline."\tboundary=\"{$this->boundaries[1]}\"".$newline.$newline; 1169 | $body .= '--'.$this->boundaries[1].$newline; 1170 | $body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline; 1171 | $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline; 1172 | $body .= $this->body.$newline.$newline; 1173 | $body .= $this->get_attachment_headers('inline', $this->boundaries[1]); 1174 | $body .= '--'.$this->boundaries[1].'--'.$newline.$newline; 1175 | $body .= '--'.$this->boundaries[0].'--'; 1176 | break; 1177 | case 'html_alt_attach': 1178 | case 'html_inline_attach': 1179 | $body .= '--'.$this->boundaries[0].$newline; 1180 | $body .= 'Content-Type: multipart/alternative;'.$newline."\t boundary=\"{$this->boundaries[1]}\"".$newline.$newline; 1181 | if (stripos($this->type, 'alt') !== false) 1182 | { 1183 | $body .= '--'.$this->boundaries[1].$newline; 1184 | $body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline; 1185 | $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline; 1186 | $body .= $this->alt_body.$newline.$newline; 1187 | } 1188 | $body .= '--'.$this->boundaries[1].$newline; 1189 | $body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline; 1190 | $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline; 1191 | $body .= $this->body.$newline.$newline; 1192 | if (stripos($this->type, 'inline') !== false) 1193 | { 1194 | $body .= $this->get_attachment_headers('inline', $this->boundaries[1]); 1195 | $body .= $this->alt_body.$newline.$newline; 1196 | } 1197 | $body .= '--'.$this->boundaries[1].'--'.$newline.$newline; 1198 | $body .= $this->get_attachment_headers('attachment', $this->boundaries[0]); 1199 | $body .= '--'.$this->boundaries[0].'--'; 1200 | break; 1201 | case 'html_alt_inline_attach': 1202 | $body .= '--'.$this->boundaries[0].$newline; 1203 | $body .= 'Content-Type: multipart/alternative;'.$newline."\t boundary=\"{$this->boundaries[1]}\"".$newline.$newline; 1204 | $body .= '--'.$this->boundaries[1].$newline; 1205 | $body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline; 1206 | $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline; 1207 | $body .= $this->alt_body.$newline.$newline; 1208 | $body .= '--'.$this->boundaries[1].$newline; 1209 | $body .= 'Content-Type: multipart/related;'.$newline."\t boundary=\"{$this->boundaries[2]}\"".$newline.$newline; 1210 | $body .= '--'.$this->boundaries[2].$newline; 1211 | $body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline; 1212 | $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline; 1213 | $body .= $this->body.$newline.$newline; 1214 | $body .= $this->get_attachment_headers('inline', $this->boundaries[2]); 1215 | $body .= $this->alt_body.$newline.$newline; 1216 | $body .= '--'.$this->boundaries[2].'--'.$newline.$newline; 1217 | $body .= '--'.$this->boundaries[1].'--'.$newline.$newline; 1218 | $body .= $this->get_attachment_headers('attachment', $this->boundaries[0]); 1219 | $body .= '--'.$this->boundaries[0].'--'; 1220 | break; 1221 | } 1222 | 1223 | } 1224 | 1225 | return array( 1226 | 'header' => $headers, 1227 | 'body' => $body, 1228 | ); 1229 | } 1230 | 1231 | /** 1232 | * Wraps the body or alt text 1233 | * 1234 | * @param string $message The text to wrap 1235 | * @param int $length The max line length 1236 | * @param string $newline The newline delimiter 1237 | * @param bool $is_html 1238 | * 1239 | * @internal param string $charset the text charset 1240 | * @internal param bool $qp_mode whether the text is quoted printable encoded 1241 | * 1242 | * @return string 1243 | */ 1244 | protected static function wrap_text($message, $length, $newline, $is_html = true) 1245 | { 1246 | $length = ($length > 76) ? 76 : $length; 1247 | $is_html and $message = preg_replace('/[\r\n\t ]+/m', ' ', $message); 1248 | $message = wordwrap($message, $length, $newline, false); 1249 | 1250 | return $message; 1251 | } 1252 | 1253 | /** 1254 | * Standardize newlines. 1255 | * 1256 | * @param string $string String to prep 1257 | * @param string $newline The newline delimiter 1258 | * 1259 | * @return string String with standardized newlines 1260 | */ 1261 | protected static function prep_newlines($string, $newline = null) 1262 | { 1263 | $newline or $newline = \Config::get('email.defaults.newline', "\n"); 1264 | 1265 | $replace = array( 1266 | "\r\n" => "\n", 1267 | "\n\r" => "\n", 1268 | "\r" => "\n", 1269 | "\n" => $newline, 1270 | ); 1271 | 1272 | foreach ($replace as $from => $to) 1273 | { 1274 | $string = str_replace($from, $to, $string); 1275 | } 1276 | 1277 | return $string; 1278 | } 1279 | 1280 | /** 1281 | * Encodes a string in the given encoding. 1282 | * 1283 | * @param string $string String to encode 1284 | * @param string $encoding The charset 1285 | * @param string $newline Newline delimeter 1286 | * 1287 | * @throws \InvalidEmailStringEncoding Encoding is not a supported by encoding method 1288 | * 1289 | * @return string Encoded string 1290 | */ 1291 | protected static function encode_string($string, $encoding, $newline = null) 1292 | { 1293 | $newline or $newline = \Config::get('email.defaults.newline', "\n"); 1294 | 1295 | switch ($encoding) 1296 | { 1297 | case 'quoted-printable': 1298 | return quoted_printable_encode($string); 1299 | case '7bit': 1300 | case '8bit': 1301 | return static::prep_newlines(rtrim($string, $newline), $newline); 1302 | case 'base64': 1303 | return chunk_split(base64_encode($string), 76, $newline); 1304 | default: 1305 | throw new \InvalidEmailStringEncoding($encoding.' is not a supported encoding method.'); 1306 | } 1307 | } 1308 | 1309 | /** 1310 | * Returns a formatted string of email addresses. 1311 | * 1312 | * @param array $addresses Array of adresses array(array(name=>name, email=>email)); 1313 | * 1314 | * @return string Correctly formatted email addresses 1315 | */ 1316 | protected static function format_addresses($addresses) 1317 | { 1318 | $return = array(); 1319 | 1320 | foreach ($addresses as $recipient) 1321 | { 1322 | $recipient['name'] and $recipient['email'] = '"'.$recipient['name'].'" <'.$recipient['email'].'>'; 1323 | $return[] = $recipient['email']; 1324 | } 1325 | 1326 | return join(', ', $return); 1327 | } 1328 | 1329 | /** 1330 | * Generates an alt body 1331 | * 1332 | * @param string $html html Body to al body generate from 1333 | * @param int $wordwrap Wordwrap length 1334 | * @param string $newline Line separator to use 1335 | * 1336 | * @return string The generated alt body 1337 | */ 1338 | protected static function generate_alt($html, $wordwrap, $newline) 1339 | { 1340 | $html = preg_replace('/[ | ]{2,}/m', ' ', $html); 1341 | $html = trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/s', '', $html))); 1342 | $lines = explode($newline, $html); 1343 | $result = array(); 1344 | $first_newline = true; 1345 | foreach ($lines as $line) 1346 | { 1347 | $line = trim($line); 1348 | if ( ! empty($line) or $first_newline) 1349 | { 1350 | $first_newline = false; 1351 | $result[] = $line; 1352 | } 1353 | else 1354 | { 1355 | $first_newline = true; 1356 | } 1357 | } 1358 | 1359 | $html = join($newline, $result); 1360 | 1361 | if ( ! $wordwrap) 1362 | { 1363 | return $html; 1364 | } 1365 | 1366 | return wordwrap($html, $wordwrap, $newline, true); 1367 | } 1368 | 1369 | /** 1370 | * Initiates the sending process. 1371 | * 1372 | * @return bool success boolean 1373 | */ 1374 | abstract protected function _send(); 1375 | 1376 | } 1377 | -------------------------------------------------------------------------------- /classes/email/driver/mail.php: -------------------------------------------------------------------------------- 1 | build_message(); 27 | $return_path = ($this->config['return_path'] !== false) ? $this->config['return_path'] : $this->config['from']['email']; 28 | if ( ! @mail(static::format_addresses($this->to), $this->subject, $message['body'], $message['header'], '-oi -f '.$return_path)) 29 | { 30 | throw new \EmailSendingFailedException('Failed sending email'); 31 | } 32 | return true; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /classes/email/driver/mailgun.php: -------------------------------------------------------------------------------- 1 | type = 'html'; 20 | 21 | $message = $this->build_message(); 22 | 23 | $config_mailgun = $this->config['mailgun']; 24 | 25 | $endpoint = isset($config_mailgun['endpoint']) ? $config_mailgun['endpoint'] : null; 26 | 27 | if (empty($endpoint)) 28 | { 29 | $mg = \Mailgun\Mailgun::create($config_mailgun['key']); 30 | } 31 | else 32 | { 33 | $mg = \Mailgun\Mailgun::create($config_mailgun['key'], $endpoint); 34 | } 35 | 36 | // Mailgun does not consider these "arbitrary headers" 37 | $exclude = array('From'=>'From', 'To'=>'To', 'Cc'=>'Cc', 'Bcc'=>'Bcc', 'Subject'=>'Subject', 'Content-Type'=>'Content-Type', 'Content-Transfer-Encoding' => 'Content-Transfer-Encoding'); 38 | $headers = array_diff_key($this->headers, $exclude); 39 | 40 | foreach ($this->extra_headers as $header => $value) 41 | { 42 | $headers[$header] = $value; 43 | } 44 | 45 | // Standard required fields 46 | $post_data = array( 47 | 'from' => static::format_addresses(array(array('email' => $this->config['from']['email'], 'name' => $this->config['from']['name']))), 48 | 'to' => static::format_addresses($this->to), 49 | 'subject' => $this->subject, 50 | 'html' => $message['body'], 51 | 'attachment' => array(), 52 | 'inline' => array() 53 | ); 54 | 55 | // Optionally cc, bcc and alt_body 56 | $this->cc and $post_data['cc'] = static::format_addresses($this->cc); 57 | $this->bcc and $post_data['bcc'] = static::format_addresses($this->bcc); 58 | $this->alt_body and $post_data['text'] = $this->alt_body; 59 | 60 | // Mailgun's "arbitrary headers" are h: prefixed 61 | foreach ($headers as $name => $value) 62 | { 63 | $post_data["h:{$name}"] = $value; 64 | } 65 | 66 | foreach ($this->attachments['attachment'] as $cid => $file) 67 | { 68 | $post_data['attachment'][] = array('filePath' => $file['file'][0], 'remoteName' => $file['file'][1]); 69 | } 70 | 71 | foreach ($this->attachments['inline'] as $cid => $file) 72 | { 73 | $post_data['inline'][] = array('filePath' => $file['file'][0], 'remoteName' => substr($cid, 4)); 74 | } 75 | 76 | // And send the message out 77 | $mg->messages()->send($config_mailgun['domain'], $post_data); 78 | 79 | return true; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /classes/email/driver/mandrill.php: -------------------------------------------------------------------------------- 1 | config['mandrill']['key']); 65 | 66 | $message = new Mandrill_Messages($mandrill); 67 | 68 | $headers = $this->extra_headers; 69 | 70 | // Get recipients 71 | $to = $this->build_rcpt(); 72 | $cc = $this->build_rcpt('cc'); 73 | $bcc = $this->build_rcpt('bcc'); 74 | 75 | $to = array_merge($bcc, $cc, $to); 76 | 77 | // Get recipient merge vars 78 | $merge_vars = array(); 79 | 80 | foreach ($this->rcpt_merge_vars as $rcpt => $_merge_vars) 81 | { 82 | $merge_vars[] = array( 83 | 'rcpt' => $rcpt, 84 | 'vars' => \Arr::keyval_to_assoc($_merge_vars, 'name', 'content'), 85 | ); 86 | } 87 | 88 | // Get recipient meta data 89 | $metadata = array(); 90 | 91 | foreach ($this->rcpt_metadata as $rcpt => $_metadata) 92 | { 93 | $metadata[] = array( 94 | 'rcpt' => $rcpt, 95 | 'values' => $_metadata, 96 | ); 97 | } 98 | 99 | // Get attachments 100 | $attachments = array(); 101 | 102 | foreach ($this->attachments['attachment'] as $cid => $attachment) 103 | { 104 | $attachments[] = array( 105 | 'type' => $attachment['mime'], 106 | 'name' => $attachment['file'][1], 107 | 'content' => $attachment['contents'], 108 | ); 109 | } 110 | 111 | // Get inline images 112 | $images = array(); 113 | 114 | foreach ($this->attachments['inline'] as $cid => $attachment) 115 | { 116 | if (\Str::starts_with($attachment['mime'], 'image/')) 117 | { 118 | $name = substr($cid, 4); // remove cid: 119 | 120 | $images[] = array( 121 | 'type' => $attachment['mime'], 122 | 'name' => $name, 123 | 'content' => $attachment['contents'], 124 | ); 125 | } 126 | } 127 | 128 | // Get reply-to addresses 129 | if ( ! empty($this->reply_to)) 130 | { 131 | $headers['Reply-To'] = static::format_addresses($this->reply_to); 132 | } 133 | 134 | $important = false; 135 | 136 | if (in_array($this->config['priority'], array(\Email::P_HIGH, \Email::P_HIGHEST))) 137 | { 138 | $important = true; 139 | } 140 | 141 | $message_data = array( 142 | 'html' => $this->body, 143 | 'text' => isset($this->alt_body) ? $this->alt_body : '', 144 | 'subject' => $this->subject, 145 | 'from_email' => $this->config['from']['email'], 146 | 'from_name' => $this->config['from']['name'], 147 | 'to' => $to, 148 | 'headers' => $headers, 149 | 'global_merge_vars' => \Arr::keyval_to_assoc($this->merge_vars, 'name', 'content'), 150 | 'merge_vars' => $merge_vars, 151 | 'metadata' => $this->metadata, 152 | 'recipient_metadata' => $metadata, 153 | 'attachments' => $attachments, 154 | 'images' => $images, 155 | 'important' => $important, 156 | ); 157 | 158 | $message_options = \Arr::filter_keys($this->get_config('mandrill.message_options', array()), array_keys($message_data), true); 159 | 160 | $message_data = \Arr::merge($message_data, $message_options); 161 | 162 | $send_options = extract($this->config['mandrill']['send_options'], EXTR_SKIP); 163 | 164 | $message->send($message_data, $async, $ip_pool, $send_at); 165 | 166 | return true; 167 | } 168 | 169 | /** 170 | * {@inheritdoc} 171 | */ 172 | public function attach($file, $inline = false, $cid = null, $mime = null, $name = null) 173 | { 174 | parent::attach($file, $inline, $cid, $mime, $name); 175 | 176 | if ($inline === true) 177 | { 178 | // Check the last attachment 179 | $attachment = end($this->attachments['inline']); 180 | 181 | if ( ! \Str::starts_with($attachment['mime'], 'image/')) 182 | { 183 | throw new \InvalidAttachmentsException('Non-image inline attachments are not supported by this driver.'); 184 | } 185 | } 186 | } 187 | 188 | /** 189 | * Add type to recipient list 190 | * 191 | * @param string $list to, cc, bcc 192 | * @return array 193 | */ 194 | protected function build_rcpt($list = 'to') 195 | { 196 | return array_map(function ($item) use ($list) 197 | { 198 | $item['type'] = $list; 199 | 200 | return $item; 201 | }, $this->{$list}); 202 | } 203 | 204 | /** 205 | * {@inheritdoc} 206 | */ 207 | protected function clear_list($list) 208 | { 209 | is_array($list) or $list = array($list); 210 | 211 | foreach ($list as $_list) 212 | { 213 | $rcpt = array_keys($this->{$_list}); 214 | \Arr::delete($this->rcpt_merge_vars, $rcpt); 215 | \Arr::delete($this->rcpt_metadata, $rcpt); 216 | } 217 | 218 | return parent::clear_list($list); 219 | } 220 | 221 | /** 222 | * Get merge vars 223 | * 224 | * @param mixed $key Null for all, string for specific 225 | * @param mixed $rcpt Null for global, string for recipient 226 | * @return array 227 | */ 228 | public function get_merge_vars($key = null, $rcpt = null) 229 | { 230 | if (is_null($rcpt)) 231 | { 232 | return \Arr::get($this->merge_vars, $key); 233 | } 234 | elseif (isset($this->rcpt_merge_vars[$rcpt])) 235 | { 236 | return \Arr::get($this->rcpt_merge_vars[$rcpt], $key); 237 | } 238 | } 239 | 240 | /** 241 | * Add merge vars 242 | * 243 | * @param array $merge_vars Key-value pairs 244 | * @param mixed $rcpt Null for global, string for recipient 245 | * @return array 246 | */ 247 | public function add_merge_vars(array $merge_vars, $rcpt = null) 248 | { 249 | if (is_null($rcpt)) 250 | { 251 | $this->merge_vars = $merge_vars; 252 | } 253 | else 254 | { 255 | $this->rcpt_merge_vars[$rcpt] = $merge_vars; 256 | } 257 | 258 | return $this; 259 | } 260 | 261 | /** 262 | * Set one or several merge vars 263 | * 264 | * @param mixed $key Array for many vars, string for one 265 | * @param mixed $value In case of many vars it is ommited 266 | * @param mixed $rcpt Null for global, string for recipient 267 | * @return object $this 268 | */ 269 | public function set_merge_var($key, $value = null, $rcpt = null) 270 | { 271 | is_array($key) or $key = array($key => $value); 272 | 273 | if (is_null($rcpt)) 274 | { 275 | $this->merge_vars = \Arr::merge($this->merge_vars, $key); 276 | } 277 | else 278 | { 279 | $merge_vars = \Arr::get($this->rcpt_merge_vars, $rcpt, array()); 280 | $this->rcpt_merge_vars[$rcpt] = \Arr::merge($merge_vars, $key); 281 | } 282 | 283 | return $this; 284 | } 285 | 286 | /** 287 | * Get metadata 288 | * 289 | * @param mixed $key Null for all, string for specific 290 | * @param mixed $rcpt Null for global, string for recipient 291 | * @return array 292 | */ 293 | public function get_metadata($key = null, $rcpt = null) 294 | { 295 | if (is_null($rcpt)) 296 | { 297 | return \Arr::get($this->metadata, $key); 298 | } 299 | elseif (isset($this->rcpt_metadata[$rcpt])) 300 | { 301 | return \Arr::get($this->rcpt_metadata[$rcpt], $key); 302 | } 303 | } 304 | 305 | /** 306 | * Add metadata 307 | * 308 | * @param array $metadata Key-value pairs 309 | * @param mixed $rcpt Null for global, string for recipient 310 | * @return array 311 | */ 312 | public function add_metadata(array $metadata, $rcpt = null) 313 | { 314 | if (is_null($rcpt)) 315 | { 316 | $this->metadata = $metadata; 317 | } 318 | else 319 | { 320 | $this->rcpt_metadata[$rcpt] = $metadata; 321 | } 322 | 323 | return $this; 324 | } 325 | 326 | /** 327 | * Set one or several metadata 328 | * 329 | * @param mixed $key Array for many, string for one 330 | * @param mixed $value In case of many it is ommited 331 | * @param mixed $rcpt Null for global, string for recipient 332 | * @return object $this 333 | */ 334 | public function set_metadata($key, $value = null, $rcpt = null) 335 | { 336 | is_array($key) or $key = array($key => $value); 337 | 338 | if (is_null($rcpt)) 339 | { 340 | $this->metadata = \Arr::merge($this->metadata, $key); 341 | } 342 | else 343 | { 344 | $metadata = \Arr::get($this->rcpt_metadata, $rcpt, array()); 345 | $this->rcpt_metadata[$rcpt] = \Arr::merge($metadata, $key); 346 | } 347 | 348 | return $this; 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /classes/email/driver/noop.php: -------------------------------------------------------------------------------- 1 | build_message(); 25 | 26 | logger(\Fuel::L_INFO, 'To: '.static::format_addresses($this->to), 'Email NoOp driver'); 27 | logger(\Fuel::L_INFO, 'Subject: '.$this->subject, 'Email NoOp driver'); 28 | logger(\Fuel::L_INFO, 'Header: '.$message['header'], 'Email NoOp driver'); 29 | logger(\Fuel::L_INFO, 'Body: '.$message['body'], 'Email NoOp driver'); 30 | 31 | return true; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /classes/email/driver/sendmail.php: -------------------------------------------------------------------------------- 1 | build_message(); 33 | 34 | // Open a connection 35 | $return_path = ($this->config['return_path'] !== false) ? $this->config['return_path'] : $this->config['from']['email']; 36 | $handle = @popen($this->config['sendmail_path'] . " -oi -f ".$return_path." -t", 'w'); 37 | 38 | // No connection? 39 | if(! is_resource($handle)) 40 | { 41 | throw new \SendmailConnectionException('Could not open a sendmail connection at: '.$this->config['sendmail_path']); 42 | } 43 | 44 | // Send the headers 45 | fputs($handle, $message['header']); 46 | 47 | // Send the body 48 | fputs($handle, $message['body']); 49 | 50 | if(pclose($handle) === -1) 51 | { 52 | throw new \SendmailFailedException('Failed sending email through sendmail.'); 53 | } 54 | 55 | return true; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /classes/email/driver/smtp.php: -------------------------------------------------------------------------------- 1 | smtp_connection) 32 | { 33 | $this->smtp_disconnect(); 34 | } 35 | } 36 | 37 | /** 38 | * The SMTP connection 39 | */ 40 | protected $smtp_connection = null; 41 | 42 | /** 43 | * Initiates the sending process. 44 | * 45 | * @return bool success boolean 46 | */ 47 | protected function _send() 48 | { 49 | // send the email 50 | try 51 | { 52 | return $this->_send_email(); 53 | } 54 | 55 | // something failed 56 | catch (\Exception $e) 57 | { 58 | // disconnect if needed 59 | if ($this->smtp_connection) 60 | { 61 | if ($e instanceOf SmtpTimeoutException) 62 | { 63 | // simply close the connection 64 | fclose($this->smtp_connection); 65 | $this->smtp_connection = null; 66 | } 67 | else 68 | { 69 | // proper close, with a QUIT 70 | $this->smtp_disconnect(); 71 | } 72 | } 73 | 74 | // rethrow the exception 75 | throw $e; 76 | } 77 | } 78 | 79 | /** 80 | * Sends the actual email 81 | * 82 | * @return bool success boolean 83 | */ 84 | protected function _send_email() 85 | { 86 | $message = $this->build_message(true); 87 | 88 | if(empty($this->config['smtp']['host']) or empty($this->config['smtp']['port'])) 89 | { 90 | throw new \FuelException('Must supply a SMTP host and port, none given.'); 91 | } 92 | 93 | // Use authentication? 94 | $authenticate = (empty($this->smtp_connection) and ! empty($this->config['smtp']['username']) and ! empty($this->config['smtp']['password'])); 95 | 96 | // Connect 97 | $this->smtp_connect(); 98 | 99 | // Authenticate when needed 100 | $authenticate and $this->smtp_authenticate(); 101 | 102 | // Set return path 103 | $return_path = empty($this->config['return_path']) ? $this->config['from']['email'] : $this->config['return_path']; 104 | $this->smtp_send('MAIL FROM:<' . $return_path .'>', 250); 105 | 106 | foreach(array('to', 'cc', 'bcc') as $list) 107 | { 108 | foreach($this->{$list} as $recipient) 109 | { 110 | $this->smtp_send('RCPT TO:<'.$recipient['email'].'>', array(250, 251)); 111 | } 112 | } 113 | 114 | // Prepare for data sending 115 | $this->smtp_send('DATA', 354); 116 | 117 | $lines = explode($this->config['newline'], $message['header'].preg_replace('/^\./m', '..$1', $message['body'])); 118 | 119 | foreach($lines as $line) 120 | { 121 | if(substr($line, 0, 1) === '.') 122 | { 123 | $line = '.'.$line; 124 | } 125 | 126 | fputs($this->smtp_connection, $line.$this->config['newline']); 127 | } 128 | 129 | // Finish the message 130 | $this->smtp_send('.', 250); 131 | 132 | // Close the connection if we're not using pipelining 133 | $this->pipelining or $this->smtp_disconnect(); 134 | 135 | return true; 136 | } 137 | 138 | /** 139 | * Connects to the given smtp and says hello to the other server. 140 | */ 141 | protected function smtp_connect() 142 | { 143 | // re-use the existing connection 144 | if ( ! empty($this->smtp_connection)) 145 | { 146 | return; 147 | } 148 | 149 | // add a transport if not given 150 | if (strpos($this->config['smtp']['host'], '://') === false) 151 | { 152 | $this->config['smtp']['host'] = 'tcp://'.$this->config['smtp']['host']; 153 | } 154 | 155 | $context = stream_context_create(); 156 | if (is_array($this->config['smtp']['options']) and ! empty($this->config['smtp']['options'])) 157 | { 158 | stream_context_set_option($context, $this->config['smtp']['options']); 159 | } 160 | 161 | $this->smtp_connection = stream_socket_client( 162 | $this->config['smtp']['host'].':'.$this->config['smtp']['port'], 163 | $error_number, 164 | $error_string, 165 | $this->config['smtp']['timeout'], 166 | STREAM_CLIENT_CONNECT, 167 | $context 168 | ); 169 | 170 | if(empty($this->smtp_connection)) 171 | { 172 | throw new SmtpConnectionException('Could not connect to SMTP: ('.$error_number.') '.$error_string); 173 | } 174 | 175 | // Clear the smtp response 176 | $this->smtp_get_response(); 177 | 178 | // Just say hello! 179 | try 180 | { 181 | $this->smtp_send('EHLO'.' '.\Input::server('SERVER_NAME', 'localhost.local'), 250); 182 | } 183 | catch(SmtpCommandFailureException $e) 184 | { 185 | // Didn't work? Try HELO 186 | $this->smtp_send('HELO'.' '.\Input::server('SERVER_NAME', 'localhost.local'), 250); 187 | } 188 | 189 | // Enable TLS encryption if needed, and we're connecting using TCP 190 | if (\Arr::get($this->config, 'smtp.starttls', false) and strpos($this->config['smtp']['host'], 'tcp://') === 0) 191 | { 192 | try 193 | { 194 | $this->smtp_send('STARTTLS', 220); 195 | if ( ! stream_socket_enable_crypto($this->smtp_connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) 196 | { 197 | throw new SmtpConnectionException('STARTTLS failed, Crypto client can not be enabled.'); 198 | } 199 | } 200 | catch(SmtpCommandFailureException $e) 201 | { 202 | throw new SmtpConnectionException('STARTTLS failed, invalid return code received from server.'); 203 | } 204 | 205 | // Say hello again, the service list might be updated (see RFC 3207 section 4.2) 206 | try 207 | { 208 | $this->smtp_send('EHLO'.' '.\Input::server('SERVER_NAME', 'localhost.local'), 250); 209 | } 210 | catch(SmtpCommandFailureException $e) 211 | { 212 | // Didn't work? Try HELO 213 | $this->smtp_send('HELO'.' '.\Input::server('SERVER_NAME', 'localhost.local'), 250); 214 | } 215 | } 216 | 217 | try 218 | { 219 | $this->smtp_send('HELP', 214); 220 | } 221 | catch(SmtpCommandFailureException $e) 222 | { 223 | // Let this pass as some servers don't support this. 224 | } 225 | } 226 | 227 | /** 228 | * Close SMTP connection 229 | */ 230 | protected function smtp_disconnect() 231 | { 232 | $this->smtp_send('QUIT', false); 233 | fclose($this->smtp_connection); 234 | $this->smtp_connection = null; 235 | } 236 | 237 | /** 238 | * Performs authentication with the SMTP host 239 | */ 240 | protected function smtp_authenticate() 241 | { 242 | // Encode login data 243 | $username = base64_encode($this->config['smtp']['username']); 244 | $password = base64_encode($this->config['smtp']['password']); 245 | 246 | try 247 | { 248 | // Prepare login 249 | $this->smtp_send('AUTH LOGIN', 334); 250 | 251 | // Send username 252 | $this->smtp_send($username, 334); 253 | 254 | // Send password 255 | $this->smtp_send($password, 235); 256 | 257 | } 258 | catch(SmtpCommandFailureException $e) 259 | { 260 | throw new SmtpAuthenticationFailedException('Failed authentication.', 0, $e); 261 | } 262 | 263 | } 264 | 265 | /** 266 | * Sends data to the SMTP host 267 | * 268 | * @param string $data The SMTP command 269 | * @param string|bool|string $expecting The expected response 270 | * @param bool $return_number Set to true to return the status number 271 | * 272 | * @throws \SmtpCommandFailureException When the command failed an expecting is not set to false. 273 | * @throws \SmtpTimeoutException SMTP connection timed out 274 | * 275 | * @return mixed Result or result number, false when expecting is false 276 | */ 277 | protected function smtp_send($data, $expecting, $return_number = false) 278 | { 279 | ! is_array($expecting) and $expecting !== false and $expecting = array($expecting); 280 | 281 | stream_set_timeout($this->smtp_connection, $this->config['smtp']['timeout']); 282 | if ( ! fputs($this->smtp_connection, $data . $this->config['newline'])) 283 | { 284 | if($expecting === false) 285 | { 286 | return false; 287 | } 288 | throw new SmtpCommandFailureException('Failed executing command: '. $data); 289 | } 290 | 291 | $info = stream_get_meta_data($this->smtp_connection); 292 | if($info['timed_out']) 293 | { 294 | throw new SmtpTimeoutException('SMTP connection timed out.'); 295 | } 296 | 297 | // Get the reponse 298 | $response = $this->smtp_get_response(); 299 | 300 | // Get the reponse number 301 | $number = (int) substr(trim($response), 0, 3); 302 | 303 | // Check against expected result 304 | if($expecting !== false and ! in_array($number, $expecting)) 305 | { 306 | throw new SmtpCommandFailureException('Got an unexpected response from host on command: ['.$data.'] expecting: '.join(' or ', $expecting).' received: '.$response); 307 | } 308 | 309 | if($return_number) 310 | { 311 | return $number; 312 | } 313 | 314 | return $response; 315 | } 316 | 317 | /** 318 | * Get SMTP response 319 | * 320 | * @throws \SmtpTimeoutException 321 | * 322 | * @return string SMTP response 323 | */ 324 | protected function smtp_get_response() 325 | { 326 | $data = ''; 327 | 328 | // set the timeout. 329 | stream_set_timeout($this->smtp_connection, $this->config['smtp']['timeout']); 330 | 331 | while($str = fgets($this->smtp_connection, 512)) 332 | { 333 | $info = stream_get_meta_data($this->smtp_connection); 334 | if($info['timed_out']) 335 | { 336 | throw new SmtpTimeoutException('SMTP connection timed out.'); 337 | } 338 | 339 | $data .= $str; 340 | 341 | if (substr($str, 3, 1) === ' ') 342 | { 343 | break; 344 | } 345 | } 346 | 347 | return $data; 348 | } 349 | 350 | } 351 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fuel/email", 3 | "description" : "FuelPHP 1.x Email Package", 4 | "type": "fuel-package", 5 | "homepage": "https://github.com/fuel/email", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "FuelPHP Development Team", 10 | "email": "team@fuelphp.com" 11 | } 12 | ], 13 | "require": { 14 | "composer/installers": "~1.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /config/email.php: -------------------------------------------------------------------------------- 1 | 'default', 28 | 29 | /** 30 | * Default setup groups 31 | */ 32 | 'setups' => array( 33 | 'default' => array(), 34 | ), 35 | 36 | /** 37 | * Default settings 38 | */ 39 | 'defaults' => array( 40 | 41 | /** 42 | * Mail useragent string 43 | */ 44 | 'useragent' => 'Fuel, a PHP Framework', 45 | 46 | /** 47 | * Mail driver (mail, smtp, sendmail, noop) 48 | */ 49 | 'driver' => 'mail', 50 | 51 | /** 52 | * Whether to send as html, set to null for autodetection. 53 | */ 54 | 'is_html' => null, 55 | 56 | /** 57 | * Email charset 58 | */ 59 | 'charset' => 'utf-8', 60 | 61 | /** 62 | * Whether to encode subject and recipient names. 63 | * Requires the mbstring extension: http://www.php.net/manual/en/ref.mbstring.php 64 | */ 65 | 'encode_headers' => true, 66 | 67 | /** 68 | * Ecoding (8bit, base64 or quoted-printable) 69 | */ 70 | 'encoding' => '8bit', 71 | 72 | /** 73 | * Email priority 74 | */ 75 | 'priority' => \Email::P_NORMAL, 76 | 77 | /** 78 | * Default sender details 79 | */ 80 | 'from' => array( 81 | 'email' => false, 82 | 'name' => false, 83 | ), 84 | 85 | /** 86 | * If set to an email address, the email driver will replace all 87 | * email addresses in to, cc, bcc and reply-to with this address 88 | * This can be used for testing purposes, to make sure no actual 89 | * emails are send out by mistake. 90 | */ 91 | 'force_to' => null, 92 | 93 | /** 94 | * Whether to validate email addresses 95 | */ 96 | 'validate' => true, 97 | 98 | /** 99 | * Auto attach inline files 100 | */ 101 | 'auto_attach' => true, 102 | 103 | /** 104 | * Auto generate alt body from html body 105 | */ 106 | 'generate_alt' => true, 107 | 108 | /** 109 | * Forces content type multipart/related to be set as multipart/mixed. 110 | */ 111 | 'force_mixed' => false, 112 | 113 | /** 114 | * Wordwrap size, set to null, 0 or false to disable wordwrapping 115 | */ 116 | 'wordwrap' => 76, 117 | 118 | /** 119 | * Path to sendmail 120 | */ 121 | 'sendmail_path' => '/usr/sbin/sendmail', 122 | 123 | /** 124 | * SMTP settings 125 | */ 126 | 'smtp' => array( 127 | 'host' => '', 128 | 'port' => 25, 129 | 'username' => '', 130 | 'password' => '', 131 | 'timeout' => 5, 132 | 'starttls' => false, 133 | 'options' => array( 134 | ), 135 | ), 136 | 137 | /** 138 | * Newline 139 | */ 140 | 'newline' => "\n", 141 | 142 | /** 143 | * Attachment paths 144 | */ 145 | 'attach_paths' => array( 146 | '', // absolute path 147 | DOCROOT, // relative to docroot. 148 | ), 149 | 150 | /** 151 | * Default return path 152 | */ 153 | 'return_path' => false, 154 | 155 | /** 156 | * Remove html comments 157 | */ 158 | 'remove_html_comments' => true, 159 | 160 | /** 161 | * Mandrill settings, see http://mandrill.com/ 162 | */ 163 | 'mandrill' => array( 164 | 'key' => 'api_key', 165 | 'message_options' => array(), 166 | 'send_options' => array( 167 | 'async' => false, 168 | 'ip_pool' => null, 169 | 'send_at' => null, 170 | ), 171 | ), 172 | 173 | /** 174 | * Mailgun settings, see http://www.mailgun.com/ 175 | */ 176 | 'mailgun' => array( 177 | 'key' => 'api_key', 178 | 'domain' => 'domain', 179 | 'endpoint' => null // optional API URL; example: 'https://api.eu.mailgun.net/v3'; to use default, omit entirely or set to null 180 | ), 181 | 182 | /** 183 | * When relative protocol uri's ("//uri") are used in the email body, 184 | * you can specify here what you want them to be replaced with. Options 185 | * are "http://", "https://" or \Input::protocol() if you want to use 186 | * whatever was used to request the controller. 187 | */ 188 | 'relative_protocol_replacement' => false, 189 | ), 190 | ); 191 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Fuel Email Package. 2 | 3 | A full fledged email class for Fuel. Send mails using php's mail function, sendmail or SMTP. 4 | 5 | # Summary 6 | 7 | * Send plain/text or html with (optional) alternative plain/text bodies using mail, sendmail or SMTP. 8 | * Add attachments, normal or inline and string or file. 9 | * Automatic inline file attachments for html bodies. 10 | * Configurable attachment paths. 11 | 12 | # Usage 13 | 14 | $mail = Email::forge(); 15 | $mail->from('me@domain.com', 'Your Name Here'); 16 | 17 | // Set to 18 | $mail->to('mail@domain.com'); 19 | 20 | // Set with name 21 | $mail->to('mail@domain.com', 'His/Her Name'); 22 | 23 | // Set as array 24 | $mail->to(array( 25 | // Without name 26 | 'mail@domain.com', 27 | 28 | // With name 29 | 'mail@domain.com' => 'His/Her Name', 30 | )); 31 | 32 | // Work the same for ->cc and ->bcc and ->reply_to 33 | 34 | 35 | // Set a body message 36 | $email->body('My email body'); 37 | 38 | // Set a html body message 39 | $email->html_body(\View::forge('email/template', $email_data)); 40 | 41 | /** 42 | 43 | By default this will also generate an alt body from the html, 44 | and attach any inline files (not paths like http://...) 45 | 46 | **/ 47 | 48 | // Set an alt body 49 | $email->alt_body('This is my alt body, for non-html viewers.'); 50 | 51 | // Set a subject 52 | $email->subject('This is the subject'); 53 | 54 | // Change the priority 55 | $email->priority(\Email::P_HIGH); 56 | 57 | // And send it 58 | $result = $email->send(); 59 | 60 | # Exceptions 61 | 62 | + \EmailValidationFailedException, thrown when one or more email addresses doesn't pass validation 63 | + \EmailSendingFailedException, thrown when the driver failed to send the exception 64 | 65 | Example: 66 | 67 | // Use the default config and change the driver 68 | $email = \Email::forge('default', array('driver' => 'smtp')); 69 | $email->subject('My Subject'); 70 | $email->html_body(\View::forge('email/template', $email_data)); 71 | $email->from('me@example.com', 'It's Me!'); 72 | $email->to('other@example.com', 'It's the Other!'); 73 | 74 | try 75 | { 76 | $email->send(); 77 | } 78 | catch(\EmailValidationFailedException $e) 79 | { 80 | // The validation failed 81 | } 82 | catch(\EmailSendingFailedException $e) 83 | { 84 | // The driver could not send the email 85 | } 86 | 87 | # Priorities 88 | 89 | These can me one of the following: 90 | 91 | + \Email::P_LOWEST - 1 (lowest) 92 | + \Email::P_LOW - 2 (low) 93 | + \Email::P_NORMAL - 3 (normal) - this is the default 94 | + \Email::P_HIGH - 4 (high) 95 | + \Email::P_HIGHEST - 5 (highest) 96 | 97 | # Attachments 98 | 99 | There are multiple ways to add attachments: 100 | 101 | $email = Email::forge(); 102 | 103 | // Add an attachment 104 | $email->attach(DOCROOT.'dir/my_img.png'); 105 | 106 | // Add an inline attachment 107 | // Add a cid here to point to the html 108 | $email->attach(DOCROOT.'dir/my_img.png', true, 'cid:my_conten_id'); 109 | 110 | 111 | You can also add string attachments 112 | 113 | $contents = file_get_contents($my_file); 114 | $email->string_attach($contents, $filename); 115 | 116 | By default html images are auto included, but it only includes local files. 117 | Look at the following html to see how it works. 118 | 119 | // This is included 120 | 121 | 122 | // This is not included 123 | 124 | 125 | 126 | Drivers 127 | ======= 128 | The drivers allow the use of this library with mostly anything that can send mails. 129 | 130 | ### Mailgun 131 | Mailgun is an online service by Rackspace (http://www.mailgun.com/) that allows you to send emails by demand. You will need to install the mailgun library (https://github.com/mailgun/mailgun-php) with composer in your FuelPHP. 132 | 133 | Once you have installed the package you will have to set up the config for your App: 134 | 135 | ```php 136 | array( 142 | 'driver' => 'mailgun', 143 | 'mailgun' => array( 144 | 'key' => 'YOUR KEY', 145 | 'domain' => 'YOUR DOMAIN', 146 | 'endpoint' => null | 'OPTIONAL ALT. API ENDPOINT URL' // e.g. 'https://api.eu.mailgun.net/v3' 147 | ), 148 | ), 149 | ); 150 | ``` 151 | 152 | # That's it. Questions? 153 | --------------------------------------------------------------------------------