Complete refactor: Moving stuff to classes, making sender tiny
Adding two config options to modify behavior Allow messages with empty body to be sent Automatically convert HTML to plain if none provided Version bump to 0.4
This commit is contained in:
parent
41a337d875
commit
2eed78b83e
5 changed files with 289 additions and 83 deletions
|
@ -35,8 +35,11 @@ class File {
|
|||
return $headers;
|
||||
}
|
||||
|
||||
function get_status($follow=true) {
|
||||
return $this->get_headers($follow)["status"];
|
||||
function get_status($follow=true, $throw=true) {
|
||||
if ($status=$this->get_headers($follow)["status"] >= 400) {
|
||||
throw new Exception("Error downloading " . $this->url . " - Status: " . $status);
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
function get_filename($follow=true) {
|
||||
|
|
95
Mail.class.php
Normal file
95
Mail.class.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
class Mail {
|
||||
private $mailer;
|
||||
|
||||
function __construct($host=null, $user=null, $password=null, $port=null) {
|
||||
global $MAIL_HOST, $MAIL_USER, $MAIL_PASS, $MAIL_STARTTLS, $MAIL_SMTPS, $MAIL_PORT;
|
||||
$this->mailer = new PHPMailer(true);
|
||||
$this->mailer->isSMTP();
|
||||
$this->mailer->AllowEmpty = true;
|
||||
$this->mailer->Host = $host ?: $MAIL_HOST;
|
||||
$this->mailer->SMTPAuth = (bool) $MAIL_USER;
|
||||
$this->mailer->Username = $user ?: $MAIL_USER;
|
||||
$this->mailer->Password = $password ?: $MAIL_PASS;
|
||||
$this->mailer->SMTPSecure = $MAIL_STARTTLS ? PHPMailer::ENCRYPTION_STARTTLS : ($MAIL_SMTPS ? PHPMailer::ENCRYPTION_SMTPS : false);
|
||||
$this->mailer->Port = $port ?: ($MAIL_PORT ?: ($MAIL_SMTPS ? 465 : 587));
|
||||
$this->mailer->XMailer = "Kumi Systems Mailer 0.1 (https://kumi.systems/)";
|
||||
}
|
||||
|
||||
function add_attachment($content, $filename=null, $cid=null) {
|
||||
$this->mailer->addStringEmbeddedImage($content, $cid, $filename);
|
||||
}
|
||||
|
||||
function add_attachments($attachments) {
|
||||
foreach ($attachments as $attachment) {
|
||||
$this->add_attachment($attachment["content"], $attachment["filename"], $attachment["cid"]);
|
||||
}
|
||||
}
|
||||
|
||||
function add_bcc($email, $name) {
|
||||
$this->mailer->addBCC($email, $name);
|
||||
}
|
||||
|
||||
function add_bccs($bccs) {
|
||||
foreach ($bccs as $bcc) {
|
||||
$this->add_bcc($bcc["email"], $bcc["name"]);
|
||||
}
|
||||
}
|
||||
|
||||
function add_cc($email, $name) {
|
||||
$this->mailer->addCC($email, $name);
|
||||
}
|
||||
|
||||
function add_ccs($ccs) {
|
||||
foreach ($ccs as $cc) {
|
||||
$this->add_cc($cc["email"], $cc["name"]);
|
||||
}
|
||||
}
|
||||
|
||||
function add_content($html=null, $text=null) {
|
||||
$this->mailer->isHTML((bool) $html);
|
||||
$this->mailer->Body = $html ?: $text;
|
||||
$this->mailer->AltBody = $html ? $text : null;
|
||||
}
|
||||
|
||||
function add_recipient($email, $name) {
|
||||
$this->mailer->addAddress($email, $name);
|
||||
}
|
||||
|
||||
function add_recipients($recipients) {
|
||||
foreach ($recipients as $recipient) {
|
||||
$this->add_recipient($recipient["email"], $recipient["name"]);
|
||||
}
|
||||
}
|
||||
|
||||
static function FromRequest($req) {
|
||||
$mail = new self();
|
||||
|
||||
$mail->set_from($req->get_sender()["email"], $req->get_sender()["name"]);
|
||||
$mail->add_recipients($req->get_recipients());
|
||||
$mail->add_ccs($req->get_ccs());
|
||||
$mail->add_bccs($req->get_bccs());
|
||||
|
||||
$html = $req->prepare_html();
|
||||
$text = $req->prepare_text(!$req->has_config("noconversion"));
|
||||
$mail->add_content($html, $text);
|
||||
|
||||
$mail->add_attachments($req->get_attachments(true, true, $req->has_config("ignoredlfails")));
|
||||
|
||||
return $mail;
|
||||
}
|
||||
|
||||
function send() {
|
||||
$this->mailer->send();
|
||||
}
|
||||
|
||||
function set_from($email, $name) {
|
||||
$this->mailer->setFrom($email, $name);
|
||||
}
|
||||
}
|
165
Request.class.php
Normal file
165
Request.class.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
require_once("config.php");
|
||||
require_once("File.class.php");
|
||||
|
||||
class Request {
|
||||
public $json;
|
||||
|
||||
function __construct($data, $authorize=True) {
|
||||
if (!$json=json_decode($data, true)) {
|
||||
throw new Exception("There is nothing here.");
|
||||
};
|
||||
|
||||
$this->json = array_change_key_case($json, CASE_LOWER);
|
||||
|
||||
if ($authorize) {
|
||||
if (!$this->authorize()) {
|
||||
throw new Exception("Who are you?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function authorize() {
|
||||
global $API_KEYS;
|
||||
return in_array($this->get_key(), $API_KEYS);
|
||||
}
|
||||
|
||||
function get_attachments($resolve=true, $content=true, $ignoredlfails=false) {
|
||||
$outachments = array();
|
||||
foreach ($this->json["attachments"] as $attachment) {
|
||||
$attachment["content"] = "";
|
||||
if ($resolve || $content) {
|
||||
$file = new File($attachment["url"]);
|
||||
if ($resolve && !$attachment["filename"]) {
|
||||
$attachment["filename"] = $file->get_filename();
|
||||
}
|
||||
if ($content) {
|
||||
try {
|
||||
$attachment["content"] = $file->fetch_file();
|
||||
} catch (\Exception $e) {
|
||||
if (!$ignoredlfails) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($attachment["content"] || !$content) {
|
||||
array_push($outachments, $attachment);
|
||||
}
|
||||
}
|
||||
return $outachments;
|
||||
}
|
||||
|
||||
function get_bccs($default=true) {
|
||||
global $ALWAYS_BCC;
|
||||
$bccs = $this->json["bccs"] ?: array();
|
||||
if ($default) {
|
||||
foreach ($ALWAYS_BCC as $bcc) {
|
||||
array_push($bccs, array("email"=>$bcc));
|
||||
}
|
||||
}
|
||||
return $bccs;
|
||||
}
|
||||
|
||||
function get_ccs($default=true) {
|
||||
global $ALWAYS_CC;
|
||||
$ccs = $this->json["ccs"] ?: array();
|
||||
if ($default) {
|
||||
foreach ($ALWAYS_CC as $cc) {
|
||||
array_push($ccs, array("email"=>$cc));
|
||||
}
|
||||
}
|
||||
return $ccs;
|
||||
}
|
||||
|
||||
function get_config() {
|
||||
return $this->json["config"];
|
||||
}
|
||||
|
||||
function get_html() {
|
||||
if ($this->json["html"]) {
|
||||
$html = $this->json["html"];
|
||||
} else if ($this->json["htmlurl"]) {
|
||||
try {
|
||||
$htmlfile = new File($this->json["htmlurl"]);
|
||||
$html = $htmlfile->fetch_file();
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception("Could not fetch URL for HTML message: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
function get_key() {
|
||||
return $this->json["key"];
|
||||
}
|
||||
|
||||
function get_placeholders() {
|
||||
return $this->json["placeholders"];
|
||||
}
|
||||
|
||||
function get_recipients($default=true) {
|
||||
global $ALWAYS_TO;
|
||||
$recipients = $this->json["recipients"] ?: array();
|
||||
if ($default) {
|
||||
foreach ($ALWAYS_TO as $recipient) {
|
||||
array_push($recipients, array("email"=>$recipient));
|
||||
}
|
||||
}
|
||||
return $recipients;
|
||||
}
|
||||
|
||||
function get_sender($fallback=true) {
|
||||
global $MAIL_FROM_MAIL, $MAIL_FROM_NAME, $MAIL_USER;
|
||||
if ($this->json["sender"]) {
|
||||
return $this->json["sender"];
|
||||
} else if ($fallback) {
|
||||
return array(
|
||||
"email" => $MAIL_FROM_MAIL ? $MAIL_FROM_MAIL : $MAIL_USER,
|
||||
"name" => $MAIL_FROM_NAME
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function get_text($conversion=true) {
|
||||
if ($this->json["text"]) {
|
||||
$text = $this->json["text"];
|
||||
} else if ($this->json["texturl"]) {
|
||||
try {
|
||||
$textfile = new File($this->json["texturl"]);
|
||||
$text = $textfile->fetch_file();
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception("Could not fetch URL for plain text message: " . $e->getMessage());
|
||||
}
|
||||
} else if ($conversion) {
|
||||
if ($html=$this->get_html()) {
|
||||
$text = html_entity_decode(
|
||||
trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
|
||||
ENT_QUOTES
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
function has_config($key) {
|
||||
return in_array($key, $this->get_config());
|
||||
}
|
||||
|
||||
function prepare_html() {
|
||||
return $this->prepare_string($this->get_html());
|
||||
}
|
||||
|
||||
function prepare_string($string) {
|
||||
foreach ($this->get_placeholders() as $placeholder) $string = str_replace("{".strtoupper($placeholder["name"])."}", $placeholder["value"], $string);
|
||||
return $string;
|
||||
}
|
||||
|
||||
function prepare_text($conversion=true) {
|
||||
return $this->prepare_string($this->get_text($conversion));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
description: A simple endpoint to send email messages
|
||||
version: '0.3'
|
||||
version: '0.4'
|
||||
title: EXPMail
|
||||
contact:
|
||||
email: support@kumi.systems
|
||||
|
@ -40,6 +40,17 @@ servers:
|
|||
- url: 'https://expmail.kumi.live'
|
||||
components:
|
||||
schemas:
|
||||
Config:
|
||||
type: object
|
||||
required:
|
||||
- key
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
description: >
|
||||
Key of the config setting
|
||||
* `noconversion` - Do not automatically convert HTML to plain text if no plain text message is explicitly given
|
||||
* `ignoredlfails` - If an attachment fails to download, just leave it out and continue processing the message
|
||||
Placeholder:
|
||||
type: object
|
||||
required:
|
||||
|
@ -89,10 +100,10 @@ components:
|
|||
description: Subject of the email
|
||||
html:
|
||||
type: string
|
||||
description: 'String containing the HTML content of the email. Takes precedence over `htmlurl` if provided. If both `html` and `text` or `texturl` are provided, will create a multi-part MIME message.'
|
||||
description: 'String containing the HTML content of the email. Takes precedence over `htmlurl` if provided. If both `html` and `text` or `texturl` are provided, or the `noconversion` config key is not provided, will create a multi-part MIME message.'
|
||||
htmlurl:
|
||||
type: string
|
||||
description: 'String containing the URL to a file containing the HTML content of the email. Ignored if `html` if provided. If both `htmlurl` and `text` or `texturl` are provided, will create a multi-part MIME message.'
|
||||
description: 'String containing the URL to a file containing the HTML content of the email. Ignored if `html` if provided. If both `htmlurl` and `text` or `texturl` are provided, or the `noconversion` config key is not provided, will create a multi-part MIME message.'
|
||||
text:
|
||||
type: string
|
||||
description: 'String containing the plain text content of the email. Takes precedence over `texturl` if provided. If both `text` and `html` or `htmlurl` are provided, will create a multi-part MIME message.'
|
||||
|
@ -128,6 +139,11 @@ components:
|
|||
description: 'Array of `Placeholder` objects. Any occurrences of `{PLACEHOLDER_NAME}` (`name` in all caps enclosed with curly brackets) in the email''s HTML or plain text will be replaced by `value`.'
|
||||
items:
|
||||
$ref: '#/components/schemas/Placeholder'
|
||||
config:
|
||||
type: array
|
||||
description: 'Array of `Config` objects to change the endpoint\'s default behaviour'
|
||||
items:
|
||||
$ref: '#/components/schemas/Config'
|
||||
key:
|
||||
type: string
|
||||
description: API key to authenticate request with
|
||||
|
|
83
sender.php
83
sender.php
|
@ -1,86 +1,13 @@
|
|||
<?php
|
||||
|
||||
require_once 'vendor/autoload.php';
|
||||
require_once 'config.php';
|
||||
require_once 'File.class.php';
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
$mailer = new PHPMailer(true);
|
||||
require_once 'Mail.class.php';
|
||||
require_once 'Request.class.php';
|
||||
|
||||
try {
|
||||
if (!$json = json_decode(file_get_contents('php://input'), true)) throw new Exception("There is nothing here.");
|
||||
|
||||
if (!$json["key"]) throw new Exception("What's the code word?");
|
||||
|
||||
if (!in_array($json["key"], $API_KEYS)) throw new Exception("Who are you?");
|
||||
|
||||
if ($json["html"]) {
|
||||
$html = $json["html"];
|
||||
} else if ($json["htmlurl"]) {
|
||||
try {
|
||||
$htmlfile = new File($json["htmlurl"]);
|
||||
$html = $htmlfile->fetch_file();
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception("Could not fetch URL for HTML message: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if ($json["text"]) {
|
||||
$text = $json["text"];
|
||||
} else if ($json["texturl"]) {
|
||||
try {
|
||||
$textfile = new File($json["texturl"]);
|
||||
$text = $textfile->fetch_file();
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception("Could not fetch URL for plain text message: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($json["placeholders"] as $placeholder) {
|
||||
$html = str_replace("{".strtoupper($placeholder["name"])."}", $placeholder["value"], $html);
|
||||
$text = str_replace("{".strtoupper($placeholder["name"])."}", $placeholder["value"], $text);
|
||||
}
|
||||
|
||||
$mailer->isSMTP();
|
||||
$mailer->Host = $MAIL_HOST;
|
||||
$mailer->SMTPAuth = (bool) $MAIL_USER;
|
||||
$mailer->Username = $MAIL_USER;
|
||||
$mailer->Password = $MAIL_PASS;
|
||||
$mailer->SMTPSecure = ($MAIL_STARTTLS ? PHPMailer::ENCRYPTION_STARTTLS : ($MAIL_SMTPS ? PHPMailer::ENCRYPTION_SMTPS : false));
|
||||
$mailer->Port = ($MAIL_PORT ? $MAIL_PORT : ($MAIL_SMTPS ? 465 : 587));
|
||||
$mailer->XMailer = "Kumi Systems Mailer 0.1 (https://kumi.systems/)";
|
||||
|
||||
if ($json["sender"]["email"]) {
|
||||
$mailer->setFrom($json["sender"]["email"], $json["sender"]["name"]);
|
||||
} else {
|
||||
$mailer->setFrom(($MAIL_FROM_MAIL ? $MAIL_FROM_MAIL : $MAIL_USER), $MAIL_FROM_NAME);
|
||||
};
|
||||
|
||||
foreach ($json["recipients"] as $recipient) $mailer->addAddress($recipient["email"], $recipient["name"]);
|
||||
foreach ($json["ccs"] as $cc) $mailer->addCC($cc["email"], $cc["name"]);
|
||||
foreach ($json["bccs"] as $bcc) $mailer->addBCC($bcc["email"], $bcc["name"]);
|
||||
|
||||
foreach ($ALWAYS_TO as $recipient) $mailer->addAddress($recipient);
|
||||
foreach ($ALWAYS_CC as $cc) $mailer->addCC($cc);
|
||||
foreach ($ALWAYS_BCC as $bcc) $mailer->addBCC($bcc);
|
||||
|
||||
$mailer->isHTML((bool) $html);
|
||||
$mailer->Subject = $json["subject"];
|
||||
$mailer->Body = ($html ? $html : $text);
|
||||
$mailer->AltBody = ($html ? $text : null);
|
||||
|
||||
foreach ($json["attachments"] as $attachment) {
|
||||
$file = new File($attachment["url"]);
|
||||
if ($file->get_status() >= 400) throw new Exception("Error downloading " . $attachment["url"] . " - Status: " . $file->get_status());
|
||||
$content = $file->fetch_file();
|
||||
$filename = ($attachment["filename"] ? $attachment["filename"] : $file->get_filename());
|
||||
$cid = ($attachment["cid"] ? $attachment["cid"] : 0);
|
||||
$mailer->addStringEmbeddedImage($content, $cid, $filename);
|
||||
}
|
||||
|
||||
$mailer->send();
|
||||
$req = new Request(file_get_contents('php://input'));
|
||||
$mail = Mail::FromRequest($req);
|
||||
$mail->send();
|
||||
|
||||
$response = array(
|
||||
"status" => "success"
|
||||
|
|
Loading…
Reference in a new issue