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.

27 comments:

Rick Schummer said...

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

Doug Hennig said...

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

Unknown said...

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!

F-Charles Waud said...

Thank you very much ... greatly appreciated!

F-Charles Waud said...

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

Doug Hennig said...

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

mbirnholz said...

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?

Doug Hennig said...

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

mbirnholz said...

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.

mbirnholz said...

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?

Doug Hennig said...

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

mbirnholz said...

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.

mbirnholz said...

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

Doug Hennig said...

That link works for me.

Doug Hennig said...

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

Doug

mbirnholz said...

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

mbirnholz said...

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

Doug Hennig said...

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

mbirnholz said...

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.

Doug Hennig said...

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

mbirnholz said...

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.

Doug Hennig said...

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.

Anthony said...

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

Doug Hennig said...

Hi Anthony.

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

Doug

Anthony said...

Hi Doug,

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

Just not working with office 2019

Doug Hennig said...

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

Doug

Anthony said...

ok, i'm using the VFPExMAPI alone.