Thursday, 17 November 2011

Ax utility function for sending mail via SMTP

The following is a quick utility function that can be used to send an email via a configured SMTP server. See comment in the code for the structure of the 'attachments' parameter. This was adapted from another code-sample, and should work with version 2009 and later.

Note this sends the mail immediately via a direct connection to the SMTP server. The preferred approach should be to create an entry in the SysOutgoingEmailTable (and SysOutgoingEmailData if attachments are required). Entries in that table are periodically scanned by the email distributor process (SysEmailDistributor), and will correctly handle errors/retries etc. See second code sample further down the post.


static public void sendMail(
    str         fromAddress,        // NB single address only
    str         toAddress,          // " "
    str         ccAddress,          // " "
    str         subject,
    str         body,
    container   attachments = conNull())
{

    // Send mail via SMTP
    // Attachments is a container of format:
    // [ Source file 1, Attachment name 1, Source file 2, Attachment name 2, ... ]

    SysEmailParameters                          emailParams = SysEmailParameters::find();

    System.Net.Mail.MailMessage                 message;
    System.Net.Mail.Attachment                  attachment;
    System.Net.Mail.AttachmentCollection        attachementCollection;
    System.Net.Mail.SmtpClient                  mailClient;
    System.Net.Mail.MailAddressCollection       addressCollection;
    str                                         mailServer;
    int                                         mailServerPort;
    str                                         attachmentFilename;
    str                                         attachmentName;
    int                                         idx;

    System.Exception                            clrException;
    InteropPermission                           perm;
    ;

    if(!fromAddress)
        throw error("From address has not been specified");
    if(!toAddress)
        throw error("To address has not been specified");

    try
    {
        perm = new InteropPermission(InteropKind::ClrInterop);
        perm.assert();

        mailServer      = emailParams.SMTPRelayServerName;
        mailServerPort  = emailParams.SMTPPortNumber;

        if(!mailServerPort)
            mailServerPort = 25;    // default SMTP port

        message = new System.Net.Mail.MailMessage(
            new System.Net.Mail.MailAddress(fromAddress,''),
            new System.Net.Mail.MailAddress(toAddress,''));

        addressCollection = message.get_CC();
        if(ccAddress)
            addressCollection.Add(ccAddress);

        message.set_Subject(subject);

        message.set_Body(body);
        attachementCollection = message.get_Attachments();

        if((conlen(attachments) mod 2) != 0)
            throw error(error::wrongUseOfFunction(funcName()));

        for(idx = 1;idx <= conLen(attachments);idx += 2)
        {
            attachmentFilename  = conPeek(attachments,idx);
            attachmentName      = conpeek(attachments,idx + 1);

            attachment = new System.Net.Mail.Attachment(attachmentFilename);
            attachment.set_Name(attachmentName);
            attachementCollection.Add(attachment);
        }

        mailClient = new System.Net.Mail.SmtpClient(mailServer,mailServerPort);
        mailClient.Send(message);

    }
    catch(Exception::CLRError)
    {
        clrException = CLRInterop::getLastException();
        error(clrException.get_Message());
        if(clrException.get_InnerException() != null)
        {
            clrException = clrException.get_InnerException();
            error(clrException.get_Message());
        }
        throw Exception::Error;
    }

}

And to test it out (assuming the method has been added to a new class called MailHelper):
MailHelper::sendMail(
  'admin@bigbusiness.com',
  'client@gmail.com',
  'linemanager@bigbusiness.com',
  'Congratulations!!',
  'You have just won an email.');

A note for developers - A handy tool for testing mailers or email-related processes can be downloaded at http://smtp4dev.codeplex.com/. It allows you to run a light-weight SMTP server locally for development and testing, and is definitely worth a look.

The following function can be used to send mail via inserting into the outgoing mail queue, utilizing the template functionality in Ax. This is a slightly modified/cleaned-up version of the method sendMail in table SysEmailTable. If specific mappings are required, pass a map<string,string> that contains token names and values. These can then be referenced in the template using the format %TOKENNAME%.

server static void insertMailToQueue(
    SysEmailId      _emailId,
    LanguageId      _language,
    str             _emailAddr,
    Map             _mappings   = null,
    container       attachments = conNull())
{
    // Adapted from SysEmailTable::sendMail

    #SysMailer
    LanguageId              languageId;

    SysEmailTable           table = SysEmailTable::find(_emailId);
    SysEmailMessageTable    message;

    str                     messageBody;
    container               data;
    container               embeddedBinaryData;

    Filename                filePath;
    Filename                filename;
    Filename                fileExtension;

    List                    list;
    ListEnumerator          enumerator;
    Filename                htmlDecodedFileName;

    int                     i = 1;
    int                     maxAttachmentSize;

    BinData                 binData;

    SysOutgoingEmailTable   outgoingEmailTable;
    SysOutgoingEmailData    outgoingEmailData;

    SysEmailBatch           batch = SysEmailBatch::construct();
    FileIOPermission        fileIOPermission;

    SysEmailParameters      emailParams = SysEmailParameters::find();
    str                     attachFilename;
    str                     attachAttachmentName;
    int                     attachmentIDx;

    ;

    maxAttachmentSize = emailParams.MaxEmailAttachmentSize;

    //maxAttachmentSize in megabytes
    if (maxAttachmentSize < 1)
        maxAttachmentSize = #maxAttachmentSizeDefault;

    if (_language)
        languageId = _language;
    else
        languageId = table.DefaultLanguage;

    message = SysEmailMessageTable::find(_emailId, languageId);

    if (!message)
        message = SysEmailMessageTable::find(_emailId, table.DefaultLanguage);

    if (!message)
        throw error(strFmt("@SYS74260", _language));

    if (message.LayoutType == SysEmailLayoutType::StaticLayout)
        messageBody = message.Mail;
    else
        throw error(strFmt("Layout type '%1' in email %2 is not supported by this function",
            message.LayoutType,
            message.EmailId));

    messageBody = SysLabel::resolveLabels(messageBody, languageId);

    ttsbegin;

    outgoingEmailTable.EmailItemId      = EventInbox::nextEventId();
    outgoingEmailTable.TemplateId       = table.EmailId;
    outgoingEmailTable.IsSystemEmail    = NoYes::No;
    outgoingEmailTable.Sender           = table.SenderAddr;
    outgoingEmailTable.SenderName       = table.SenderName;
    outgoingEmailTable.Recipient        = _emailAddr;

    //note: first do the xml transform if needed, params are substitued after that
    messageBody = SysEmailMessage::stringExpand(messageBody, SysEmailTable::htmlEncodeParameters(_mappings));
    messageBody = strReplace(messageBody, '\n', '<br>');
    [outgoingEmailTable.Message, data] = SysEmailTable::embedImages(messageBody);

    list        = List::create(data);
    enumerator  = list.getEnumerator();
    while (enumerator.moveNext())
    {
        htmlDecodedFileName = SysEmailTable::htmlDecode(enumerator.current());

        fileIOPermission = new FileIOPermission(htmlDecodedFileName,'r');
        fileIOPermission.assert();
        //BP Deviation Documented
        if (WinAPIServer::fileExists(htmlDecodedFileName) &&
            //BP Deviation Documented
            (WinAPIServer::fileSize( htmlDecodedFileName) < (maxAttachmentSize * 1000000)) &&
            SysEmailTable::isFromAttachmentsFolder(htmlDecodedFileName))
        {
            binData = new BinData();
            //BP Deviation Documented
            binData.loadFile(htmlDecodedFileName);
            embeddedBinaryData = binData.getData();

            outgoingEmailData.EmailItemId       = outgoingEmailTable.EmailItemId;
            outgoingEmailData.DataId            = i;
            outgoingEmailData.EmailDataType     = SysEmailDataType::Embedded;
            outgoingEmailData.Data              = embeddedBinaryData;
            [filePath, filename, fileExtension] = Global::fileNameSplit(htmlDecodedFileName);

            outgoingEmailData.FileName          = int642str(outgoingEmailTable.EmailItemId) + '_' + int2str(i);
            outgoingEmailData.FileExtension     = fileExtension;

            outgoingEmailData.insert();

            i++;
        }

        CodeAccessPermission::revertAssert();
    }

    outgoingEmailTable.Subject = SysEmailMessage::stringExpand(message.Subject, _mappings);

    outgoingEmailTable.Priority         = table.Priority;
    outgoingEmailTable.WithRetries      = false;
    outgoingEmailTable.RetryNum         = 0;
    outgoingEmailTable.UserId           = curUserId();
    outgoingEmailTable.Status           = SysEmailStatus::Unsent;

    for(attachmentIDx = 1;attachmentIDx <= conLen(attachments);attachmentIDx += 2)
    {
        attachFilename          = conPeek(attachments,attachmentIDx);
        attachAttachmentName    = conPeek(attachments,attachmentIDx + 1);

        fileIOPermission = new FileIOPermission(attachFilename,'r');
        fileIOPermission.assert();

        if (attachFilename && WinAPIServer::fileExists(attachFilename))
        {
            if (WinAPIServer::fileSize(attachFilename) < (maxAttachmentSize * 1000000))
            {
                binData = new BinData();
                binData.loadFile(attachFilename);
                embeddedBinaryData = binData.getData();

                outgoingEmailData.EmailItemId       = outgoingEmailTable.EmailItemId;
                outgoingEmailData.DataId            = i;
                outgoingEmailData.EmailDataType     = SysEmailDataType::Attachment;
                outgoingEmailData.Data              = embeddedBinaryData;
                [filePath, filename, fileExtension] = Global::fileNameSplit(attachFilename);
                outgoingEmailData.FileName          = attachAttachmentName;
                outgoingEmailData.FileExtension     = fileExtension;

                outgoingEmailData.insert();
            }
        }
    }

    CodeAccessPermission::revertAssert();

    outgoingEmailTable.LatestStatusChangeDateTime = DateTimeUtil::getSystemDateTime();
    outgoingEmailTable.insert();

    ttscommit;
}

No comments:

Post a Comment