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 9 has an email + attachment to send and the preferences are set to send via SMTP, the job is usually performed by Built_In:__SendSMTPMail.

If there is a script handler in your document 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")

In MoneyWorks 9, your SendSMTPMail handler can call internal handlers Built_In:__SendSMTPMail(to, path, subject, message_text, attachmentName) or Built_In:__SendSMTPMail_WithCredentials(to, path, subject, msg, attachmentName,
server, replyTo, authUser, authPass)
to do the heavy lifting for you. Note that these handlers are open source and can be found in the Preload_Built_In.mwscript file in the Standard Plugins Scripts/Autoload folder (also copied below)

Note that prior to v9, if there was no user defined handler, an external executable (sendEmail) was used. SendEmail(.exe) is no longer installed with MoneyWorks 9, so if you have scripts that rely on it, you may need to update them.

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 the source of the built in handler (in MoneyWorks 9.0.2). You can copy/paste this into your document as a starting point, but to have it override the built-in handlers, you should remove the leading underscores from __SendSMTPMail and __SendSMTPMail_WithCredentials.


constant meta = "MoneyWorks Built-in MWScript handler library"

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

    When MoneyWorks has an email + attachment to send and the preferences are
    set to send via SMTP...
    
    IF there is a script handler named SendSMTPMail, then that handler 
    will be called to send the email.
    
    OTHERWISE, Built_In:__SendSMTPMail will be called
    
    Your SendSMTPMail handler MAY call Built_In:__SendSMTPMail or
    Built_In:__SendSMTPMail_WithCredentials
    
    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

// Use Preload_Built_In:SetVerbose(1) to get CURL logging output in the log file to debug connections

property verbose = 0

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

property pl_offset

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

on Read_Payload_Callback(bytesdata)
    let thispayload = Mid(payloadpl_offset + 1bytesIN_BYTES)
    let pl_offset = pl_offset + bytes
    return thispayload
end

on Curl_WriteHandler(bytesdata)
    
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(m78)
    let nl = Position(line"\r")
    if nl == 0
        let nl = Position(line"\n")
    endif
    if nl <> 0
        return Left(linenl - 1) + " " // include space to indicate that the linebreak is a soft one
    endif
    if Length(line) == 78
        while Right(line1) <> " " and Position(line" ") > 0
            let line = Left(lineLength(line) - 1)
        endwhile
    endif
    return line
end

on AppendPayload(line)
    let payload = payload + line + "\r\n"
end

on CleanEmail(ad)
    let lt = PositionInText(ad"<")
    let gt = PositionInText(ad">")
    if lt and gt and gt > lt
        return Mid(adlt + 1gt - lt - 1)
    endif
    return trim(ad)
end

on __SendSMTPMail_WithCredentials(topathsubjectmessage_textattachmentNameserverreplyToauthUserauthPasspublic

    let res = ""
        
    /*
     *  Mail headers
     */
     
    let to = CleanEmail(to)
     
    let payload = ""
    AppendPayload("Date: " + DateToText(Time(), DateFormRFC2822))
    // 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)
    AppendPayload("To: " + to)
    AppendPayload("From: " + replyTo)
    AppendPayload("Reply-To: " + CleanEmail(replyTo))
    AppendPayload("Message-ID: <" + MakeGUID() + "@" + server + ">")
    AppendPayload("Subject: " + subject)
    AppendPayload("User-Agent: MoneyWorks/"+version+"_SendSMTPMail_libcurl")
    
    /*
     *  MIME header
     */

    AppendPayload("MIME-Version: 1.0")
    let MIME_bound = "MW-"+MakeGUID()
    AppendPayload("Content-Type: multipart/mixed; boundary=" + MIME_bound)


    AppendPayload("")
    AppendPayload("This is a message with multiple parts in MIME format.")
    AppendPayload("--" + MIME_bound)
    
    /*
     *  Add the text/plain MIME section with the message
     */
     
    AppendPayload("Content-Transfer-Encoding: quoted-printable")
    AppendPayload("Content-Type: text/plain; charset=utf-8; format=fixed")
    AppendPayload("")
    
    // linebreak the message (chunk_split() fn would be nice)
    
    let mtext = message_text
    while length(mtext)
        let a_line = GetLineFromMessage(mtext)

        // convert the line to quoted-printable
        // passing = as delim to URLEncode will only encode non-printable-ASCII and =
        AppendPayload(URLEncode(a_line"="))
        
        let mtext = Right(mtextLength(mtext) - Length(a_line))
    endwhile
    AppendPayload(" ")
    
    //syslog(payload)

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

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

        AppendPayload(b64)
    endif

    /*
     *  MIME end marker
     */

    AppendPayload("--" + MIME_bound + "--")
    
    /*
     *  Now use CURL to send...
     */
    
    
    let curl = curl_init()
    if PositionInText(server":465")
        curl_setopt(curlCURLOPT_URL"smtps://" + server)
    else
        curl_setopt(curlCURLOPT_URL"smtp://" + server)
    endif
    curl_setopt(curlCURLOPT_USE_SSL1)
    curl_setopt(curlCURLOPT_VERBOSEverbose)
     
    if authUser != ""
        curl_setopt(curlCURLOPT_USERNAMEauthUser)
        curl_setopt(curlCURLOPT_PASSWORDauthPass)
    endif

    /* 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(curlCURLOPT_MAIL_FROMCleanEmail(replyTo))
 
    /* Iterate over comma-separated  recipient list (an explode() fn would be nice)
     * 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     // 
        // The recipient must be a pure email address; extract that if we have "Name <addr>" format
        // (the foreach in text will already do in implicit trim of whitespace)
        let r = rcpt
        if PositionInText(r"<"and PositionInText(r">")
            let r = Mid(rPositionInText(r"<") + 1PositionInText(r">") - PositionInText(r"<") - 1)
        endif
        let recipients[i] = Trim(r)
        let i = i + 1
    endfor

    if GetAppPreference("bccToSelf")
        let recipients[i] = CleanEmail(GetAppPreference("replyTo"))
        syslog("added bcc to " + recipients[i])
    endif

    curl_setopt(curlCURLOPT_MAIL_RCPTrecipients)
    
 
    /* 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(curlCURLOPT_READFUNCTION"Read_Payload_Callback")
    curl_setopt(curlCURLOPT_UPLOAD1)
    curl_setopt(curlCURLOPT_WRITEFUNCTION"Curl_WriteHandler")   // Having this gets us a numeric curlcode as a result from curl_exec
    
    curl_setopt(curlCURLOPT_SSL_OPTIONS2)   // = CURLSSLOPT_NO_REVOKE Windows needs this for gmail
    
    let pl_offset = 0

    /* Send the message */ 
    let res = curl_exec(curl);
    curl_close(curl);
    
     // the text "[INFO] Email was sent successfully" MUST be logged for MW to consider the operation succeeded
    if(res == 0)
        syslog("[INFO] Email was sent successfully to " + to)
    else
        syslog("[ERROR] " + curl_strerror(res))
    endif

end

on __SendSMTPMail(topathsubjectmessage_textattachmentNamepublic
        
    /*
     *  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")
    if GetAppPreference("useAuth")
        let authUser = GetAppPreference("smtpUser")
        let authPass = GetAppPreference("smtpPass")
    else
        let authUser = ""
        let authPass = ""
    endif

    __SendSMTPMail_WithCredentials(topathsubjectmessage_textattachmentNameserverreplyToauthUserauthPass)
end

on SetVerbose(vpublic
    let verbose = v
end
Posted in MWScript, Sample Code | Comments Off on SendSMTPMail