SendSMTPMail

SendSMTPMail is a script handler you can implement that takes over the job of sending SMTP mail.
The handler signature is:

SendSMTPMail(recipients, attachmentPath, subject, message, attachmentFileName)

When MoneyWorks has an email + attachment to send and the preferences are set to send via SMTP, the job is usually handed off to the sendEmail perl script.

In v8.1.7 and later, if there is a script handler named SendSMTPMail, then that handler will be called instead (note: if there are multiple such handlers, they will all be called, potentially resulting in the message being sent multiple times)

  • recipients may be a single recipient or a comma-delimited list
  • attachmentPath is the posix or windows path to the file to be attached
  • subject is the email subject
  • message is the plain text message; may be blank
  • attachmentFileName is a suggested attachment name which may differ from the actual filename of the attachmentPath (e.g. "Invoice 1234.pdf")

Automatically called: When a message needs to be sent via SMTP

Use for: Customising SMTP sending behaviour

Availability: MoneyWorks Gold 8.1.7 and later.

Below is an example proof-of-concept handler that you can use as a starting point for your custom SMTP handler

constant meta = "SMTP override Proof-of-Concept — Rowan Daniell"

/********************************************************************************

    When MoneyWorks has an email + attachment to send and the preferences are
    set to send via SMTP, the job is usually handed off to the sendEmail perl script.

    In v8.1.7, IF there is a script handler named SendSMTPMail, then that handler 
    will be called instead (note: if there are multiple such handlers, they will 
    all be called, potentially resulting in the message being sent multiple times)

    handler signature:
    ------------------------------------------------------------------------------
    SendSMTPMail(recipients, attachmentPath, subject, message, attachmentFileName)
    ------------------------------------------------------------------------------

    - recipients may be a single recipient or a comma-delimited list
    - attachmentPath is the posix or windows path to the file to be attached
    - subject is the email subject
    - message is the plain text message; may be blank
    - attachmentFileName is a suggested attachment name which may differ from the
        actual filename of the attachmentPath (e.g. "Invoice 1234.pdf")


**********************************************************************************/


// Build the email message in this proprty

property payload


// Callback to deliver payload to libcurl; don't return more than the requested number of bytes

on Read_Payload_Callback(bytes, data)
    let thispayload = Left(payload, bytes, IN_BYTES)
    let payload = Right(payload, Length(payload, IN_BYTES) - length(thispayload, IN_BYTES), IN_BYTES)
    return thispayload
end

// Break lines between words

on GetLineFromMessage(m)
    // Try to break at <= 78 characters (although RFC 821 allows up to 1000, it recommends 78 + \r\n)
    let line = Left(m, 78)
    let nl = Position(line, "\r")
    if nl == 0
        let nl = Position(line, "\n")
    endif
    if nl <> 0
        return Left(line, nl - 1) + " " // include space to indicate that the linebreak is a soft one
    endif
    if Length(line) == 78
        while Right(line, 1) <> " " and Position(line, " ") > 0
            let line = Left(line, Length(line) - 1)
        endwhile
    endif
    return line
    
    /*
    
    to-do: better line breaking per RFC2646
    
           A generating agent SHOULD:

              1.  Ensure all lines (fixed and flowed) are 79 characters or
                  fewer in length, counting the trailing space but not
                  counting the CRLF, unless a word by itself exceeds 79
                  characters.
              2.  Trim spaces before user-inserted hard line breaks.
              3.  Space-stuff lines which start with a space, "From ", or
                  ">".
                  
        -- maybe quoted-printable?

    */
end

on SendSMTPMail(to, path, subject, message, attachmentName)

    /*
     *    Get the server settings from preferences;
     *
     *    These could be overridden (in particular the From: might want to be overridden with the logged-in user,
     *    NOTE that the SMTP server must accept email from that user; most servers won't accept
     *    email from addresses in other domains. There is no magical way to change that.)

     */

    let server = GetAppPreference("smtp")
    let replyTo = GetAppPreference("replyTo")
    let authUser = GetAppPreference("smtpUser")
    let authPass = GetAppPreference("smtpPass")
    
    /*
     *    Mail headers

     */
    
    let payload = "Date: " + DateToText(Time(), DateFormRFC2822) + "\r\n"
    // send To: all recipients. Could sort them into To:, CC:, BCC: if you want, using some criteria
    // (maybe if the address is prefixed with cc: or bcc:, parse that)
    let payload = payload + "To: " + to + "\r\n"
    let payload = payload + "From: " + authUser + "\r\n"
    let payload = payload + "Message-ID: <" + MakeGUID() + "@" + server + ">\r\n"
    let payload = payload + "Subject: " + subject + "\r\n"
    
    /*
     *    MIME header

     */

    let payload = payload + "MIME-Version: 1.0\r\n"
    let MIME_bound = "MoneyWorks-MIME-"+MakeGUID()
    let payload = payload + "Content-Type: multipart/mixed; boundary="+ MIME_bound + "\r\n"


    let payload = payload + "\r\n"
    let payload = payload + "This is a message with multiple parts in MIME format.\r\n"
    let payload = payload + "--" + MIME_bound + "\r\n"
    
    /*
     *    Add the text/plain MIME section with the message

     */
     
    let payload = payload + "Content-Transfer-Encoding: 8bit\r\n"
    let payload = payload + "Content-Type: text/plain; charset=UTF-8; format=flowed\r\n"
    let payload = payload + "\r\n"
    
    // linebreak the message (chunk_split() fn would be nice)
    
    let mtext = message
    while length(mtext)
        let a_line = GetLineFromMessage(mtext)
        let payload = payload + a_line + "\r\n"
        let mtext = Right(mtext, Length(mtext) - Length(a_line))
    endwhile
    let payload = payload + " \r\n"

    /*
     *    Add the attachment, with the suggested attachment name

     */

    let content_type = "application/octet-stream"
    if Position(path, ".pdf") <> 0
        let content_type = "application/pdf"
    endif
    let payload = payload + "--" + MIME_bound + "\r\n"
    let payload = payload + "Content-Type: " + content_type + "; name=\"" + attachmentName + "\"\r\n"
    let payload = payload + "Content-Transfer-Encoding: base64\r\n"
    let payload = payload + "\r\n"
    let fd = File_Open(path)
    let b64 = Base64Encode(File_Read(fd))
    File_Close(fd)

    while length(b64, IN_BYTES)
        let a_line = Left(b64, 78, IN_BYTES)
        let payload = payload + a_line + "\r\n"
        let b64 = Right(b64, Length(b64, IN_BYTES) - Length(a_line, IN_BYTES), IN_BYTES)
    endwhile

    /*
     *    MIME end marker

     */

    let payload = payload + "--" + MIME_bound + "--\r\n"
    
    /*
     *    Now use CURL to send...

     */
    
    
    let curl = curl_init()
    curl_setopt(curl, CURLOPT_URL, "smtps://" + server);
    curl_setopt(curl, CURLOPT_USERNAME, authUser);
    curl_setopt(curl, CURLOPT_PASSWORD, authPass);

    /* Note that this option isn't strictly required, omitting it will result
     * in libcurl sending the MAIL FROM command with empty sender data. All
     * autoresponses should have an empty reverse-path, and should be directed
     * to the address in the reverse-path which triggered them. Otherwise,
     * they could cause an endless loop. See RFC 5321 Section 4.5.5 for more
     * details.

     */ 
    curl_setopt(curl, CURLOPT_MAIL_FROM, replyTo)
 
    /* Iterate over comma-separated  recipient list
     * It is irrelevant which are To, CC, BCC — that cosmetic information is provided in headers.

     */ 
     
    let recipients = CreateArray()
    let i = 0
    foreach rcpt in text to        // 
        let recipients[i] = rcpt
        let i = i + 1
    endfor
    curl_setopt(curl, CURLOPT_MAIL_RCPT, recipients)
 
    /* We're using a callback function to specify the payload (the headers and
     * body of the message). You could just use the CURLOPT_READDATA option to

     * specify a FILE pointer to read from. */ 
    curl_setopt(curl, CURLOPT_READFUNCTION, "Read_Payload_Callback")
    curl_setopt(curl, CURLOPT_UPLOAD, 1)
 
    /* Send the message */ 
    let res = curl_exec(curl);
    
    if(res == "")
        say("Email sent")
    else
        Alert(res)
    endif

    curl_close(curl);
end
Posted in MWScript, Sample Code | Comments Off on SendSMTPMail