Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Local Personal SMTP Server

Status
Not open for further replies.

stanlyn

Programmer
Sep 3, 2003
945
US
Hi,

I'm looking for a small and local (on same machine) SMTP service or app that always runs outside of VFP on the same machine so that when I send an outgoing email from my VFP app, this service or app intercepts and contacts the actual smtp server for delivery. My app remains completely responsive at all times even when there are network, smtp or other external issues. I'm using it to send error emails with screenshots back to us.

Currently I send it to the smtp server directly from VFP and because now I'm including screenshots in the emails, the app is tied up for way too long, like 30-40 seconds for a 3.5mb screenshot file. I want the app to only prepare the email and hand it off to this ??? (that is outside my app) and continue doing what the app does with zero user delays...

I briefly look at the "VFP Parallel" processing articles a while back and wondering if that is overkill for this task?

I also looked at SmtpQ product from ChilKat which does exactly what I want, except it is only available in 64b...

Something simple is preferred?

Any ideas?
Stanley
 
And one more detail about the RUN command:

There is no WITH like DO has and there is no way to pass variables by their names only. When you write a RUN command, the part after it is a commandline and the comandline interface allows you to pass values, every parameter is space separated instead of comma separated and there is an overall size limitation, so you won't pass your mailtext via parameter. I already recommemended a parameterless call, so you don't have to think about this at all, you simply let the EXE know what i needs to know by code or by configuration.

To share configuration: You can share code libraries from your main exe, if you simply also add them to a new PJX, so you keep in sync after building both EXEs and still only have one codebase in regard of class libraries reading in config files, for example. Thus can also share config data without the need to copy anything, your mailsender.EXE in the same directory reads the smae config data via same code and so you share configuration knowledge parameterless.

Bye, Olaf.
 
Hi Olaf,

Olaf said:
To share configuration: You can share code libraries from your main exe, if you simply also add them to a new PJX, so you keep in sync after building both EXEs and still only have one codebase in regard of class libraries reading in config files, for example. Thus can also share config data without the need to copy anything, your mailsender.EXE in the same directory reads the smae config data via same code and so you share configuration knowledge parameterless.

Can you point me to some docs on how to do this, step-by-step, as this is totally new stuff for me.

My new "EmailSender project has a single prg named EmailSender.prg and the ChilKay's smpt library. I've compiled it into both an .app and .exe.

It works when calling it via the .app with the parameter list, and as you said, it will not as an exe.

So what exactly do I need to add the the new sender project? The only items that is related in the main app is masterprocs.prg, erroremailer.prg, and _main.prg.

I am also passing a lcBody which is quite long as well.

Thanks,
Stanley


 
Hi Olaf,

Olaf said:
I already recommemended a parameterless call

I've looked back up this thread looking for your mentioned references and see nothing on how its done. Please point it out.

Thanks,
Stanley
 
to do something in a parameterless way, you need to find a way to pass things to another executable
using something other than command line parameters.

So, if you have program A call program B but need to pass a load of data (say email address, subject line, message content and list of attachments)
you might code it so that program A creates a specific text file and program B is able to read that file - it (the text file) having a known format
that would enable program B to identify the various elements and extract them.

Regards

Griff
Keep [Smile]ing

There are 10 kinds of people in the world, those who understand binary and those who don't.

I'm trying to cut down on the use of shrieks (exclamation marks), I'm told they are not good for you.
 
Hi Griff,

OK, thats easy enough, but Olaf was talking about a concept where program B knows about program A variables somehow by adding program A's resources to B and compile...

Thanks,
Stanley
 
Stanlyn,

I copy some things I said alread:

The seperate EXE could either be self contained and know what mails to send, eg processing the errorlog.dbf entries not yet marked as sent, or you prepare the mails in a DBF as a job table for that EXE

To share configuration: You can share code libraries from your main exe, if you simply also add them to a new PJX, so you keep in sync after building both EXEs and still only have one codebase in regard of class libraries reading in config files, for example. Thus can also share config data without the need to copy anything, your mailsender.EXE in the same directory reads the smae config data via same code and so you share configuration knowledge parameterless.

your mailsender EXE will simply use ADIR on a knwo directory of just the files you want to send. Or you use a DBF, or you use XML, or a SQL Server table or whatever you like.

So overall, the communication between your main EXE and the bugreporting EXE is done via file system and/or configuration and/or data or any combination of your liking and your competences. Just starting the bugreporting EXE it starts its job by reading in config and processing a DBF or SQL Server table or a folder.

You have current code sending mails, don't you? That code does screenshots, doesn't it? That code also puts together a mail text, doesn't it? You write the screenshot as PNG or JPG to a file, don't you? You do error loggin into a DBF, not? If not, then simply do so and you also have data for your new EXE to process. Then you also know STRTOFILE() to save text or html to a file, if you don't want to use a memo field for that, don't you? So you have all ingredients and more than you need for a new EXE to get at all the things it needs to compose mails and send them.

You can put the RUN of your mailsender.EXE into the mailsend function or PRG you have in your main EXE and that's it. The new mailsend function of your old EXE will take the parameters as it always did and all code you have4 using theis function stays. You only change one single place and moce the code using CDO.Message or however you sent mails so far into the nex EXE.

Here's what SMTPQ is mainly:
Code:
Do while .t.
   processdir()
   * sleep
Enddo

Procedure processdir()
FOR lnI = 1 TO ADIR(laFiles,somedir)
   if sendmail(laFiles[lnI,1]) 
      ERASE (laFiles[lnI,1]) 
      * or
      * COPY FILE (laFiles[i,1]) To (somedir+".\sent\"+JUSTFNAME(laFiles[lnI,1])
      * ERASE (laFiles[lnI,1]) 
    else
      COPY FILE (laFiles[i,1]) To (somedir+".\failed\"+JUSTFNAME(laFiles[lnI,1])
      ERASE (laFiles[lnI,1]) 
   endif
ENDFOR

Most probably more complex, more configurable, etc. But mainly that's what it says it does, process files in a dir and putting failed sends somewhere.

Bye, Olaf.
 
OK, Olaf,

I have it working except for this issue.

I have the sender compiled into sender.exe from sender.prg. The prg has a path set as follows: set path to '.\, .\data, .\temp'

From within the main.prg, I launch sender.exe with run /n 'sender.exe'

Then the main program calls it, the sender.exe runs as expected and sends the mail. The problem is it still ties up the main VFP program, UNLESS I am stepping thru the main programs code that launches the sender.exe. If I take the debugger out of the equation, then VFP is tied up. Is there any special environment settings that needs to be used?

Update, sometimes its fast and more often it ties up the main.prg. I have noticed that the debugger is not causing the issue as I've had it tie it up also. Any ideas?

Thanks,
Stanley
 
Run /N will start a new process and /N means continuing right away with the current code. So I don't see how you even could debug this, you run the EXE code, not prg code.
The startup of an EXE costs a second, perhaps, so you shouldn't need to start it for every single mail, but do batch processing within the sender.exe.

I don't know how you think a relative pathing .\,.\data and .\temp is used within the sender.exe, it's its own process, so it has no idea about your main EXE current path. That's another reason I said you share configuration. You can of course take relative paths from the sender.exes own current directory, which typically starts off as the directory the EXE is located within, and thus would be the same as you main.exe default path, if you put them side by side, but all that is not about tieup, just about addressing files.

What do you see as tieup? The length of the send duration doesn't change, it's the bottleneck of the upload speed you can't speed up, but separating the sending into a new process makes your main.exe continue and be responsive right away.

Bye, Olaf.

 
About the /N (NOWAIT) switch, see the big difference about it here:

Code:
Run /n notepad.exe
MessageBox("notepad started")

Code:
Run notepad.exe
MessageBox("notepad ended")

Put each in a prg and start. The second one waits for notepad.exe to be closed. Besides you'll see a shell window, as notepad.exe isn't started directly, but via command.com or cmd.exe (%COMSPEC%).

RUN /N doesn't make any use of this shell layer, it's the simplest way to start another windows application EXE without waiting for it, you can also use API calls or WSH helper objects to start an EXE, but RUN /N is not depending on a PIF file, on Foxrun.PIF and all alike old stuff, that might be in your way.

Starting a separate process of course also takes its time, but it'll not tie up your own process for half a minute or longer. If you still see a tie up, then it might be because of the concurrent use of LAN bandwidth, that you can't circumvent by sending the mail in a second process, but we already saw this at max can take away 512 kbit, far less than 100Mbit or 1Gbit. I don't see what else would make your main.exe really tie up. Or you have any other reason for the tie up of the system, eg the new process still running on the same CPU core. I'd not put too much weight on that, though, as even a single core cpu in the past did things in parallel, the CPU and Windows OS are quite good at switching threads and processes.

Bye, Olaf.
 
You might give a try on starting with /N7, which means the new sender.exe process will be started "inactive". That doesn't mean it's fully asleep and inactive, but it means the focus stays on your process, your current form stays active, the sending will take place anyway.

Bye, Olaf.
 
Hi Olaf,

Some more testing reveals...
Since last night and no code changes, running the main program's error routine hands the sending job to the sender.exe and control was returned to the main program in less than 2 seconds, which is good. I see the sender.exe at work in the task manager and it auto closes in about 30-40 seconds, again as expected.

Now the sender.exe is no longer showing in the task manager, I generate a new error in the main program and this time the main program is tied up for about 15 seconds and sender.exe is showing in the task manager and auto closes in another 15 -25 seconds. What is causing this tieup?

Now in the time it has taken me to respond to your posts, I tested again and this time the main program was tiedup for the whole 30-40 seconds and sender.exe auto closed about the same time that control was returned to the main.prg.

Here is the MailSender.prg code is built into an exe:
Code:
* MailSender.prg
* 2016/01/21

Set Path To '.\, .\DATA, .\TEMP'

Use 'Message' In 0 Shared
Select 'Message'
Count All For success_timestamp = {/:} ;
	.And. Alltrim(message_type) = 'EMAIL' To lnCount

If lnCount > 0
	Set Filter To success_timestamp = {/:} ;
		.And. Alltrim(message_type) = 'EMAIL'
	Go Top

	Do While !Eof('Message')
		lcSmtpServer = smtp_server
		lnSmtpPort = smtp_port
		lcSmtpUserName = smtp_userid
		lcSmtpPassword = smtp_password
		lnSmtpUseSSL = Iif(smtp_use_ssl = .T., 1, 0)
		lnSmtpStartTLS = Iif(smtp_start_tls = .T., 1, 0)
		lcFromOffice = from_office
		lcFromAddress = from_address
		lcToName = to_name
		lcToAddress = to_address
		lcSubject = subject
		lcBody = body
		*lcFile1Attachment = Alltrim(file1_path)
		*lcFile1Binary = Strtofile(file1_bin, lcFile1Attachment+'.png', 0)
		lcFile1Binary = Strtofile(file1_bin, Alltrim(file1_path), 0)
		lcFile1Attachment = Alltrim(file1_path)

		Release loMailman, loEmail, lnSuccess

		If Type('lcSmtpServer') = 'L'
			Messagebox('Smtp Server is Invalid for MessageID: ' + ;
				ALLTRIM(Str(Message.pk, 6, 0)) + ', Exiting...')
			Replace try_count With try_count + 1
			Skip 1 In 'Message'
			Loop
		Endif

		Local loMailman, lnSuccess, loEmail

		*  The mailman object is used for sending email.
		loMailman = Createobject('Chilkat_9_5_0.MailMan')
		lnSuccess = loMailman.UnlockComponent("xxxxxxx")
		If (lnSuccess <> 1)
			Wait Clear
			Messagebox(loMailman.LastErrorText, 0, 'Chilkat License Error...')
			Release loMailman, loEmail, lnSuccess
			Return
		Endif

		*Set the SMTP server/login/password (if required)
		loMailman.SmtpHost = lcSmtpServer
		loMailman.SmtpPort = lnSmtpPort
		loMailman.SmtpUsername = lcSmtpUserName
		loMailman.SmtpPassword = lcSmtpPassword
		loMailman.SmtpSsl = lnSmtpUseSSL
		loMailman.StartTLS = lnSmtpStartTLS

		*  Create a new email object
		loEmail = Createobject('Chilkat_9_5_0.Email')
		loEmail.subject = lcSubject
		loEmail.body = lcBody
		loEmail.From = lcFromOffice + ' <' + lcFromAddress + '>'
		loEmail.AddTo(lcToName, lcToAddress)

		lcContentType = loEmail.AddFileAttachment(lcFile1Attachment)
		If (Isnull(lcContentType))
			Messagebox(loEmail.LastErrorText, 0, 'Attachment Was Not Added...')
			*Release loMailman, loEmail, lnSuccess
			*Return
		Else
			*Messagebox(loEmail.LastErrorText, 0, 'Attachment Was Added')
		Endif

		lnSuccess = loMailman.SendEmail(loEmail)
		If (lnSuccess <> 1)
			Messagebox(loMailman.LastErrorText, 0, 'Email Sending Error...')
		Else
			Replace success_timestamp With Datetime()
			*Messagebox('A support email was sent to: ' + lcToAddress + '...', 0, 'Done...', 5000)
		Endif

		Replace try_count With try_count + 1

		*  Some SMTP servers do not actually send the email until
		*  the connection is closed.  In these cases, it is necessary to
		*  call CloseSmtpConnection for the mail to be  sent.
		*  Most SMTP servers send the email immediately, and it is
		*  not required to close the connection.

		lnSuccess = loMailman.CloseSmtpConnection()
		If (lnSuccess <> 1)
			Messagebox("Connection to SMTP server did not close properly.", 0, "Error...", 30000)
		Else
			*Messagebox("Connection to SMTP server Did Close Properly.", 0, "Success...", 30000)
		Endif

		Release loMailman, loEmail, lnSuccess

		Erase(".\temp\MainMessage.txt")
		Erase( (lcFile1Attachment) )

		Skip 1 In 'Message'
	Enddo
Endif

Use In Select('Message')


And now calling code that leads up to the call from the main.program:

Code:
Wait Clear
Wait 'Emailing Error Message to: ' + lcToAddress + ' via the mail server at: ' + lcSmtpServer+ '...' Window Nowait

lcFile1Attachment = pcImageName
llCloseMessage = .F.

If !Used('Message')
	Use 'Message' In 0 Shared
	llCloseMessage = .T.
Endif

Select 'Message'
Append Blank
Replace system_id With Alltrim(SystemDB.system_id)
Replace computer_name With Alltrim(Sys(0))
Replace time_stamp With Datetime()
Replace app_title With gcAppTitle
Replace Module With gcModuleName
Replace message_type With 'EMAIL'
Replace source_table With ''
Replace source_userid With lcUser
Replace smtp_server With lcSmtpServer
Replace smtp_port With lnSmtpPort
Replace smtp_userid With lcSmtpUserName
Replace smtp_password With lcSmtpPassword
Replace smtp_use_ssl With Iif(lnSmtpUseSSL = 0, .F., .T.)
Replace smtp_start_tls With Iif(lnSmtpStartTLS = 0, .F., .T.)
Replace from_office With lcFromOffice
Replace from_address With lcFromAddress
Replace to_name With 'CPOS Support'
Replace to_address With lcToAddress
Replace subject With lcSubject
Replace body With lcBody
Replace file1_path With lcFile1Attachment
Replace file1_bin With Filetostr(lcFile1Attachment)
Replace try_count With 0

If llCloseMessage = .T.
	Use In Select('Message')
Endif

Erase( (lcFile1Attachment) )

lcZZ = 'MailSender.Exe'
Run /N &lcZZ

Wait Clear
Wait "Creating an event log entry into the EventLog table..." Window Nowait


>> relative pathing
This is all working as expected as the sender.exe is following the relative paths correctly. After byilding the exe, I copy it to the main program's root folder and the .\data and .\temp are seen and used correctly.


>> upload speed
This solution should not care about that.

Thanks,
Stanley
 
I don't have the time to read your sender.exe code right now, but it shouldn't matter what code any exe you call via RUN /N has in it, RUN does start the EXE and doesn't wait for it at all, the main EXE continues (NOWAIT) with it's own code.

I don't know what you do to measure the runtime of things. Put this around the RUN call:

Code:
t1=seconds()
RUN /N sender.exe
t2=seconds()
Messagebox(t2-t1)

This should be split seconds.

All other time your main.exe needs it needs for all its other code but sending mails, unless you forgot to take out calls to your old mailing code in the exe. If this time span is the 15 to 30 seconds you sometimes said it takes, you have some weird thing about your hard drive perhaps. Starting an exe, especially quite a small exe, even if it also needs to load the VFP runtime and C runtime DLLs, shouldn't take that long.

Bye, Olaf.
 
Here are 2 small screenshots showing the speed as 30.xx seconds when running:

Code:
t1=seconds()
lcZZ = 'MailSender.Exe'
Run /N &lcZZ
t2=seconds()
Messagebox('Seconds: ' + TRANSFORM(t2-t1))

Clearly tieing it up...

Stanley
 
 http://files.engineering.com/getfile.aspx?folder=338c6ed8-a56d-4f0d-abf6-65dfe04866ca&file=Picture0007.png

Code:
run /n7

Makes no difference in the tieup...

Stanley
 
Fixed it with shellexec from Mike's site... (see screenshot)

Code:
t1=seconds()
lcZZ = 'MailSender.Exe'

DECLARE INTEGER ShellExecute IN shell32.dll ; 
  INTEGER hndWin, ; 
  STRING cAction, ; 
  STRING cFileName, ; 
  STRING cParams, ;  
  STRING cDir, ; 
  INTEGER nShowWin

cFileName = (lcZZ) 
cAction = "open" 
ShellExecute(0, cAction, cFileName, "", "", 1)

*Run /N7 &lcZZ
t2=seconds()
Messagebox('Seconds: ' + TRANSFORM(t2-t1))

Any idea why the run command was failing?

Thanks,
Stanley

 
 http://files.engineering.com/getfile.aspx?folder=fccf9373-08a4-4235-892f-e961d5bbdc40&file=Picture0008.png
RUN /N is doing the same, just simpler (obviously far less code), so no, I don't see why it would take longer to RUN /N than to ShellExecute() the EXE.
I actually can't even believe it's the problem.

There is one thing you should do, when using ShellExecute: Use the full path to the MailSender.EXE. RUN looks into the curdir, ShellExecute has no idea about VFPs curdir. This makes a difference, if Mailsender.exe is in the windows search path and found fast, and it's taking VFP long to find the EXE via RUN , if it's not in the current dir.

What is ADIR(laDummy,Sys(5)+Sys(2003)+'MailSender.Exe') at the moment before RUN? Is it 0?
If it is 0, then it means the EXE is not in the current directory, eg you SET DEFAULT or CDed somewhere else, and the OS ShellExecute function is faster in finding the EXE than VFP is.

You can fix that, obviously by CDing into the folder of the Mailsender.exe or by specifying a full path. Know about SYS(5), SYS(2003), SYS(16), _VFP.ServerName and where they point to. You can always find out where your main.exe is located by SYS(16,0), for example. Cautious, though, at designtime this will be the main.prg of your project, not the EXE. A way to make this unimportant is having the main.prg, your main start prg, in the same directory as the pjx and also building the EXE there (as normal). Then JUSTPATH(SYS(16,0)) always is the path of your main.exe or main.prg and this is also where you find mailsender.exe, if you put it side by side, as suggested.

Bye, Olaf.
 
One other obvious thing: If it really takes 30 seconds to just start the EXE and that's even not mainly because it takes VFP so long to find it, you would have a hdd problem. Or the whole system is tied up and everything has a performance problem including just doing a RUN. Then this tie up may in turn be the reason for the error triggtering your error handler triggering the screenshot and running the mailsender in the first place and not vice versa.

I don't see how VFP could disregard the /N switch and wait for the EXE to finish, unless you simply didn't executed RUN /N or you did so in the debugger in single step mode and waited before executing t2=seconds(). Is your EXE folder in the network?

Bye, Olaf.

 
Olaf,

>> If it really takes 30 seconds to just start the EXE
Pathing and finding Sender is not the issue here. While testing, I put a messagebox("I'm in the sender exe") at the top of the .prg. When running the main program and forcing an error, the messagebox shows immediately (no delay). The only difference is "run /n" ties up the main program for 30 seconds.

Did you not see the screenshots of both good & bad elapsed times I sent?

>> Is your EXE folder in the network?
No, locally on Solid State Drive under Win8-64 and Intel I7.

>> unless you simply didn't executed RUN /N
Take a look for yourself, as I sent the code...

>> you did so in the debugger in single step mode and waited
You cannot run the debugger on an exe...

>> What is ADIR(laDummy,Sys(5)+Sys(2003)+'MailSender.Exe') at the moment before RUN? Is it 0?
Yes, it is 0.
I do not know what the ADIR(laDummy,Sys(5)+Sys(2003)+'MailSender.Exe') is doing. I just went back and put the "set step on" command just before the run command. Your ADIR command returns a 0, like I said and you suspected. Also to confirm its location, curdir() returns '\cpos\server\' as expected, because it is the apps root folder. The MailServer.exe file IS in this root folder along with main.exe and folders such as data, temp and etc. The actual disk location for MailSender.exe is D:\CPOS\Server\MailSender.exe and Main.exe is D:\CPOS\Server\Main.exe

Like I've already said, there is no delay with MailSender.exe starting as the messagebox confirms that with both run /n and shellexe... Only difference is run /n ties up the main and shellexe does not.

Thanks,
Stanley
 
>> you did so in the debugger in single step mode and waited
> You cannot run the debugger on an exe...
You can't step into the mailsender.exe, but you may run the RUN code in the debugger to visuzalize the timing the lines need, doesn't matter.

>> What is ADIR(laDummy,Sys(5)+Sys(2003)+'MailSender.Exe') at the moment before RUN? Is it 0?
> Yes, it is 0.

Then the mailsender.exe is not in the current directory and the "tieup" is simply the time VFP needs to find the MailSender.Exe.

I said you should know what SYS(5), SYS(2003), and more are. If you don't, the VFP help is a good reference. In short this is putting together the current directory path+"mailsender.exe" and checking, if this file exists by using ADIR. As ADIR doesn't find it the resulting array is empty and ADIR returns the array size, so 0 means not found.

So your RUN causes VFP to not find Mailsender.exe and therefore search for the EXE and this takes that long. My starting assumption is you put your main.exe and mailsender.exe into one directory side by side and never CD anywhere or never SET DEFAULT to some path, so the current and working directory is the mainapp.exe directory and RUN /N mailsender.exe is sufficient.

If your mailsender.exe is in the same directory as your mainapp.exe, you obviously need to CD back to the EXE directory or RUN with the full path to the mailsender.exe for the RUN to work immediately. SYS(5)+SYS(2003) obviously is the wrong directory. In a first attempt you might simply hardcode your path. Then look back what I said about SYS(16,0).

ShellExecute is somehow finding the mailsender.exe faster than VFP, I wouldn't take that for granted, though.

If all this doesn't apply to you, then the big question would be, why ADIR doesn't find the exe. You should perhaps analyze your SSD, if it's still healthy.

Bye, Olaf.
 
>If all this doesn't apply to you, then the big question would be, why ADIR doesn't find the exe
It might fail on a missing backslash, eg [tt]ADIR(laDummy,AddBS(Sys(5)+Sys(2003))+'MailSender.Exe')[/tt] would be 1.
If it is, it's unclear, why RUN would be so slow.

Bye, Olaf.

 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top