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
    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

on Curl_WriteHandler(bytesdata)

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

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

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

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("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")
    // 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 =
        let mtext = Right(mtextLength(mtext) - Length(a_line))
    AppendPayload(" ")

     *  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"
        AppendPayload("--" + MIME_bound)
        if attachmentName != ""
            AppendPayload("Content-Type: " + content_type + "; name=\"" + attachmentName + "\"")
            AppendPayload("Content-Type: " + content_type)
        AppendPayload("Content-Transfer-Encoding: base64")
        let fd = File_Open(path)
        let b64 = Base64Encode(File_Read(fd), true)


     *  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)
        curl_setopt(curlCURLOPT_URL"smtp://" + server)
    if authUser != ""

    /* 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.
    /* 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)
        let recipients[i] = Trim(r)
        let i = i + 1

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

    /* 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_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);
     // 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)
        syslog("[ERROR] " + curl_strerror(res))


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")
        let authUser = ""
        let authPass = ""


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