SMTPMail

Summary: Send automated e-mails via your SMTP server
Version: 20231002
Prerequisites: PmWiki 2.2.107 (for Notify), curl, UTF-8
Status: Experimental
Maintainer: Petko
License: GPLv2+
Categories: SystemTools PHP72
Users: (view? / edit)
Discussion: SMTPMail-Talk

Send automated e-mails via your SMTP server instead of the built-in PHP function "mail()".

This will likely only work on servers with a GNU/Linux operating system.

Description

E-mails sent from a hosting plan directly with the mail() function will sometimes get labeled as spam or simply rejected by the mail servers of the recipients. This is more probable if your wiki is on a shared hosting and other clients have abused this function and got the hosting's IP addresses blacklisted.

This function connects to your actual SMTP server, authenticated with a username and password, and sends the e-mails like you do it with your e-mail software or webmail.

This is a lightweight solution (much lighter than other existing PHP-only libraries). It relies on the cURL utility which needs to be installed on the server and callable by the PHP process (it is available on most Linux hosting plans).

The recipe will work for the Notify feature since PmWiki 2.2.107, but it doesn't require any particular PmWiki version in order to work e.g. for another addon.

Installation

  • Copy smtpmail.php to your pmwiki/cookbook directory. Please make sure you have the latest version.
  • Add to config.php this code:
include_once("$FarmD/cookbook/smtpmail.php");
MailSMTP(array(
  'server' => 'smtps://smtp.gmail.com:465',
  'userpass' => 'USERNAME:PASSWORD',
  'from' => 'wiki@example.com',
  'bcc' => 'webmaster@example.com',
  'curl' => 'curl',
  'options' => ' --ssl -k --anyauth ',
));
$MailFunction = "MailSMTP";

Configuration

  • $MailFunction = "MailSMTP"; # This causes PmWiki and addons who implement $MailFunction to use this feature instead of the mail() PHP function.

To configure the SMTP server and authentication, call the function MailSMTP with a single argument an array of the settings, explained below. The reason to not set global variables is to reduce the risk your username and password to become available by mistake, e.g. via the "diag" action.

The settings array can contain the following entries:

  • 'server' => 'smtps://smtp.gmail.com:465', # This is the URI for the mail server, starting with the smtp: or smtps: (secure) protocol, then the server domain or IP address, then the port. The example server works for GMail/GSuite, check the documentation for your company e-mails and/or your internet provider.
  • 'userpass' => 'USERNAME:PASSWORD', # The username (sometimes e-mail address) and password to use when connecting to the server.
    Note that for Gmail, as of 2023 you may need to create a unique "app password", separate from the real one. See this page in their documentation.
  • 'from' => 'wiki@example.com', # REQUIRED. The e-mail address for the "From:" field, usually the e-mail address for the account; sometimes you need to use the actual address of the mailbox that is authenticated, or the server may reject or rewrite the header.
  • 'bcc' => 'me@example.com', # Add a Bcc: recipient to all outgoing messages, default empty.
  • 'curl' => 'curl', # The path to the curl executable.
  • 'options' => ' --ssl -k --anyauth ', # Additional options to pass to the curl command.
  • 'CountFunction' => 'myCounterCallback', # a function to call with the total number of recipients, including To:, CC:, and BCC: headers.

You can call MailSMTP with different settings array depending on the page, group, or user. These will be remembered when sending mail with subsequent calls.

Usage

To use it for the Notify feature, you'll need PmWiki 2.2.107 or newer, and the line $MailFunction = "MailSMTP"; in config.php, no other interaction is required.

To call the function from your addon, use the same arguments as the mail() PHP function:

  $sent = MailSMTP($to, $subject, $message, $headers);

Or, use the global $MailFunction if you don't know whether SMTPMail is installed:

  SDV($MailFunction, 'mail');
  $sent = $MailFunction($to, $subject, $message, $headers);

The function should return true if the message was sent, false otherwise.

How to debug

Sending SMTP mail can be tricky, and debugging may not be obvious. There are ways to check for warnings/errors.

You send a message with such a line:

  $sent = MailSMTP($to, $subject, $message, $headers);

If the $sent variable is false, the sending failed. You can then examine 2 global variables:

  • $SMTPMail['curloutput'] should contain the response of the curl command with potentially information about what didn't work.
  • $SMTPMail['debug'] may contain other useful information.

How to send rich text HTML mail with attachments

Note that UTF-8 needs to be enabled on the wiki for this function.

The recipe includes a helper function that can format a multipart HTML message with attachments. Here is an example how to use it.

# Array containing the message parts:
$parts = [];

# First part, message text
# Embed pictures with the "cid:" prefix instead of "Attach:"
$parts[] = "markup:Hello,

Your message written as wiki markup that would be converted to HTML.

Best wishes,
Your Support Team

%width=120% cid:logo240.png\"Logo\" %%
Got a question? Find answers via our 
[[Main/FAQ]], [[Main/Help centre]],
or [[mailto:support@example.com|contact us]].";

# Relative path to the picture file referenced in the markup:
$parts[] = "cid:uploads/Main/logo240.png"; # for cid:logo240.png above

# Relative path to another file to be attached to the message:
$parts["newsletter.pdf"] = "file:uploads/Main/newsletter.pdf";

# Or, supply the attachment as data:
$parts["report.csv"] = "content:" . $raw_content_of_the_attachment;

# Add any CSS styles to be added to the rich text message:
$SMTPMailStylesFmt['my_message'] = '
  .mystyle1 { color: #888; }
  .mystyle2 { color: #800; }
  a { color: #f40; }
';

# Create the multipart message and the required headers:
list($message, $headers) = MultipartMailSMTP($parts, $pagename);

# Optionally, add some custom headers:
$headers .= "
Bcc: me@example.com
Reply-To: support@example.com
";

# Send the message:
$result = MailSMTP($to, $subject, $message, $headers);
# $result: true or 1 if success, false or 0 if failure

Notes about the markup:

  • Email clients only support a limited set of HTML/CSS and no JavaScript. I recommend to check how your message looks at least in Gmail, Outlook, RoundCube and Thunderbird, and use only the commonly supported HTML and CSS.
    • PmWiki pages includes many styles from many CSS files (core, skin, local), these are not automatically available in the message. You can add styles in the array $SMTPMailStylesFmt before calling MultipartMailSMTP().
  • Wiki links to pages and Attach: links to files will work in the message if on your wiki the HTML source has full links including the website host name. Otherwise use the full URLs in the links.
  • Attachment extensions needs to be defined in $UploadExts.
    • Some email servers will block messages that have some attachment types like executable or scripting files, including inside ZIP/TGZ archives.
  • Attach: links to embedded pictures may fail to load as many email clients now block external pictures, and the user sees a link or a button to enable these. A better chance of an embedded picture showing in the message is to add the file as attachment and refer to it via the "cid:" prefix, see next line.
  • To embed a picture attached to the message, use the custom "cid:" prefix like "cid:filename.jpg", then add a message part that contains "cid:path/to/filename.jpg".
    • Some email clients don't allow embedding of some image types, notably SVG may not show at all.

Notes

The recipe is experimental, please monitor the Sent folder of your mailbox and report any problems.

To enable sending SMTP mails from GMail/GSuite, you may need to allow "less-secure" apps, and possibly to disable 2-factor authentication for that mailbox. You may want to do this for a specific mailbox separate from your main one.

Note that for Gmail, as of 2023 you may need enable 2FA and create a unique "app password", separate from the real one. See this page in their documentation.

The actual SMTP password is written in clear in the config files, you should estimate the risks.

The recipe has not been tested in many environments, it is theoretically possible to have false positives or false negatives as a return value (like with 'mail'). Monitor your Sent folder.

While Gmail saves the sent messages in the Sent folder, other email servers may not do this. You can use a "bcc:" header to send copies of the sent messages to a mailbox you can monitor.

Many email providers will only allow a limited number of messages to be sent per 24 hours, or per hour, and all recipients in To:, CC:, and BCC: are counted. If the MailSMTP function works for some time then suddenly fails, this may be the reason.

Change log / Release notes

  • 20231002 Fix warning with undefined $LinkPattern. Add $SMTPMail["debug"], $SMTPMail["curloutput"].
  • 20230702 Escape some arguments, add $SMTPMailStylesFmt.
  • 20230430 Add $CountFunction, add $IMap["cid"] for embedded pictures.
  • 20230302 Improve handling of some headers; keep html parts without base64-encoding them.
  • 20230225 Update Multipart headers to prevent inline images duplicated in attach list on some email clients.
  • 20230224 Installation code changed for more flexibility. (Old configuration should still work but is considered deprecated.) Added header "Message-Id:" which is recommended to reduce the chance of messages flagged as spam. Add helper function to create multipart messages with plain text, wiki markup, html, embedded pictures, and attached files. (To be documented.)
  • 20200309 Fix bug that didn't allow recipient addresses with apostrophes.
  • 20180112 Add function SMTPMailOpt(), change installation code.
  • 20180107 first public release, ready to be tested.

See also

Contributors

Written and maintained by Petko.

Comments

See discussion at SMTPMail-Talk

User notes? : If you use, used or reviewed this recipe, you can add your name. These statistics appear in the Cookbook listings and will help newcomers browsing through the wiki.