Monday, October 26, 2020

Sending Email from VFP Applications: Supporting Newer SMTP Protocols

There are many libraries you can use to send emails from VFP applications. I created my own several years ago: a C# wrapper class using the .NET SMTPClient class, which I call using Rick Strahl's wwDotNetBridge. However, as discussed here, Microsoft no longer recommends using this class because it doesn't support modern protocols (which I can attest to, thanks to several support tickets from customers). As suggested in the article, I've rewritten my wrapper class to use the open source MailKit project.

Here's some code that shows how to use this wrapper class:

local loMail, ;
   llReturn
loMail = newobject('SFMail', 'SFMail.prg')
with loMail
   .cServer      = 'MyMailServer.com'
   .cUser        = 'MyUserName'
   .cPassword    = 'MyPassword'
   .nSMTPPort    = 25
   .cSenderEmail = .cUser
   .cSenderName  = 'My Name'
   .cRecipients  = 'someone@somewhere.com'
   .cSubject     = 'Test email'
   .cBody        = 'This is a test message. ' + ;
      '<strong>This is bold text</strong>. ' + ;
      '<font color="red">This is red text</font>'
   .cAttachments = 'koko.jpg'
   llReturn      = .SendMail()
   if llReturn
      messagebox('Message sent')
   else
      messagebox('Message not sent: ' + .cErrorMessage)
   endif llReturn
endwith

Here's what the received email looks like. Notice the attached image and the formatted text.

The class supports the following:

  • Text or HTML body: simply include HTML tags in the cBody property and SFMail automatically handles it
  • Attachments: cAttachments is a comma-separated list of file names
  • Normal, CC (the cCCRecipients property), and BCC recipients (the cBCCRecipients property): separate multiple email addresses with commas or semi-colons
  • SMTP or MAPI (using Craig Boyd's VFPExMAPI.fll): set lUseMAPI to .T. to use MAPI or .F. (the default) to use SMTP
  • Adjustable timeout (the nTimeout property, which is in seconds)
  • Adjustable security settings (the nSecurityOptions property; see the MailKit documentation for the values). I find the default setting of 1 (Auto) works best for most servers.
  • Diagnostic logging: set cLogFile to the name and pass of a log file; it will contain the dialog between MailKit and the mail server so you can debug any problems.

You can download SFMail and the supporting files from the Technical Papers page of my web site or directly from here.

32 comments:

  1. What version of .NET has to be deployed/ensured on the user's computer?

    ReplyDelete
  2. It needs .NET 4.6 or later because it needs TLS 1.2 support.

    ReplyDelete
  3. Doug! Thank you for this! I first heard of the issue back in July and couldn't find a way forward. Decided to leave it on the back burner, but now this will be a piece of cake. I owe you a beer at the very least the next time I see you!

    ReplyDelete
  4. Thank you very much ... greatly appreciated!

    ReplyDelete
  5. Doug, does this also solve the Google oauth changes we discussed several months ago?

    ReplyDelete
  6. I knew you were going to ask that 😁. No, I looked into the GMail API and while the MailKit library can help with it, it's a lot more involved than a simple SMTP call. It's still on my list of things to do.

    Doug

    ReplyDelete
  7. This appears to be exactly what my platform needs. Not sure where I have been hiding! Any chance you have a link to a step by step for installation for use in a WWWC project?

    ReplyDelete
  8. Not much to do: assuming you're already using wwDotNetBridge (if not, read the docs for it), just copy the DLLs into your program folder and call the SFMail class in the PRG as needed.

    Doug

    ReplyDelete
  9. I initially downloaded the complete source on git for 'mailkit'. After your message I looked at your initial post more closely and downloaded the sfmail.zip which is going to be no sweat at all.

    ReplyDelete
  10. Doug - sorry to bug you with two issues. But here is some feedback.

    1. I copied all the dll's and fll into my projects root folder and added the sfmail.prg to my project but I am unable to compile. I get an "Unable to find unknown EMCREATEMESSAGEEX" error. If I ignore all, my exe is built and when I test, it does in fact work.

    2. Lastly, a key requirement for me is setting the 'reply to email' which is different than the auth email or user id email. Any chance there is a property for that I am missing?

    ReplyDelete
  11. 1. It looks like you didn't copy VFPExMAPI.fll; that's used for MAPI support. If you don't need that, you could remove the SendMailMAPI method in SFMail.prg.

    2. I didn't have such a property but it was easy to add: cReplyTo. Download SFMail.zip again and you should find that new property (the changes were to SMTPLibrary2.dll and SFMail.prg).

    Doug

    ReplyDelete
  12. Doug you rock!

    I tried to get the update from - https://doughennig.com/Papers/Pub/SFMail.zip ? This link is on the page you mentioned in the WWWC post. I think you put it somehwhere else.

    ReplyDelete
  13. Not sure if you saw my other post yet about the updated zip not being avail to where I initially downloaded it. Please paste url.

    Also, I can confirm the the vfpexmapi.fll is in fact in my path. I did go ahead and comment out the mapi stuff just in case but same issue.

    procedure SendMail
    local llReturn

    *if This.lUseMAPI
    * llReturn = This.SendMailMAPI()
    * else
    llReturn = This.SendMailSMTP()
    * endif This.lUseMAPI
    return llReturn
    endproc

    ReplyDelete
  14. That link works for me.

    ReplyDelete
  15. Commenting out the call to SendMailMAPI doesn't help: you need to remove the method altogether.

    Doug

    ReplyDelete
  16. I should have said YES the link works, but the updated files you mentioned are not updated ;)

    ReplyDelete
  17. It's there now (wink) - will leave you alone now!

    ReplyDelete
  18. You need to clear your browser cache -- it's giving you the old one you downloaded.

    ReplyDelete
  19. Thanks once again Doug. I have the latest code and was able to get the mapi stuff commented (I would like to try that though in the future).

    Sorry to say that the cReplyto property while there, is not doing what I had hoped (emails when being replied to are being sent to the .cuser property. I think the web servers have changed their acceptance of this value in an effort to stop spam.

    I tested on gmail and office 365 so far.

    ReplyDelete
  20. I just tried it with Office 365 and the mail server for my web site and both worked for me. Are you sure you're setting cReplyTo to the reply to address?

    Doug

    ReplyDelete
  21. 100% sure.

    I found an article that suggests that as of.net 4 the property is no longer cReplyto but rather cReplyToList (seems wrong as well) but I thought you might be interested in reviewing it.

    https://docs.microsoft.com/en-us/dotnet/api/system.net.mail.mailmessage.replytolist?view=net-5.0

    I should also mention that WWWC's sendmail is behaving the same way. I set the cReplyTo property but in outlook the reply to is the 'senderemail' instead of cReplyTo.

    ReplyDelete
  22. That article is about a completely different class. ReplyTo is the correct property for MailKit.

    Set cLogFile to the name and path of a file (such as Log.txt). After sending the message, that file will contain the messages between the client and server. Maybe that'll help you track down the issue.

    ReplyDelete
  23. Anthony8:17 PM

    Hello

    I have been using VFPExMAPI.fll in my vfp applications and due to an upgrade to windows 10
    and office 2019 this no longer works.

    Would like to implement this as a solution to the problems I’m having.

    Do you know If VFPExMAPI.fll can be used under windows 10? And I am just missing something?

    Thank you for your time Doug

    Anthony

    ReplyDelete
  24. Hi Anthony.

    VFPExMAPI.fll works fine for me with Windows 10 and Office 365.

    Doug

    ReplyDelete
  25. Anthony5:50 PM

    Hi Doug,

    Could you show me some sample code on a simple send email?

    Just not working with office 2019

    ReplyDelete
  26. Download the SFMail class mentioned in this blog post: its SendEmailMAPI method uses VFPExMAPI.

    Doug

    ReplyDelete
  27. Anthony1:41 AM

    ok, i'm using the VFPExMAPI alone.

    ReplyDelete
  28. Hi Doug,
    I've tried the code below:
    **********************
    Set Procedure To sfmail additive
    Set Procedure To wwdotnetbridge additive

    local loMail, ;
    llReturn
    loMail = createobject('SFMail')
    with loMail
    .cServer = 'srv69.niagahoster.com'
    .cUser = 'regy@nico.co.id'
    .cPassword = 'myPassword'
    .nSMTPPort = 25
    .cSenderEmail = .cUser
    .cSenderName = 'Regy Zhong'
    .cRecipients = 'regy@bionesia.net'
    .cSubject = 'Test e-mail reminder from VFP'
    .cBody = 'This is a test message. ' + ;
    'This is bold text.'
    *!* .cAttachments = getfile('jpg')
    llReturn = .SendMail()
    if llReturn
    messagebox('Message sent')
    else
    messagebox('Message not sent: ' + .cErrorMessage)
    endif llReturn
    endwith

    And received this error:
    A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed respond 45.13.133.51:25

    ReplyDelete
  29. Try changing nSecurityOptions to another value; see http://www.mimekit.net/docs/html/T_MailKit_Security_SecureSocketOptions.htm for the values. Also, make sure cServer, nSMTPPort, and other settings are correct.

    ReplyDelete
  30. Hi Doug, thank you.
    I change the port from 25 to 587 and now it's working perfectly.

    Thank you so much for sharing this class _/\_

    ReplyDelete
  31. Hey Doug! Thanks for this. I've been utilizing it for a good six months or so, but apparently my customers haven't as I just ran into a fairly annoying issue - the attachment files get locked open. If I want to delete the file (using DELETE FILE) after sending (especially ics files that are temporary), I get a File access is denied. I have to shut down my app to be able to delete the file, even from disk. Luckily, I can overwrite the file if I need to, but that isn't leaving things the cleanest I possibly can. I know I can probably do a close all to unlock things, but that'll prevent execution of the other things I have going on. I've also tried releasing the loMail object. Do you have any other ideas?

    ReplyDelete
  32. I suggest using the SFMail project on VFPX (https://github.com/DougHennig/SFMail) as it's a bit newer and resolves this issue.

    ReplyDelete