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:

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.

Unknown said...

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

Doug Hennig said...

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.

Unknown said...

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 _/\_

Unknown said...

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?

Doug Hennig said...

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