Attached is a set of routines that you can use in your FF/PB apps for sending email that I recently put together. Although I've only run about 200 test messages through, it seems pretty solid. I purposely used Jose's includes and tried to include support for the %UNICODE declarative. There is a DLL I wrote in assembler some years back that is used, primarily for picking up attachment files and B64 encoding them. You can likely replace that whole bit with the Afx routines Jose provides for B64 support. I included the ASM source and DEF file that I used with MASM32 to build the DLL to ease any concerns going forward about me getting pushed under some truck.
I also included support for DNS MX queries. This will allow you to emulate a basic SMTP server and send emails with no server dependencies or you can plug in your own server and use that one.
It's not the most robust implementation feature wise although it does support attachments, HTML mail and most of the standard headers.
If you find any problems or errors (maybe "when" is a better description;-)), please share it on these marvelous forums for all to use.
Here is some sample code using MX lookups. You have to put in some names/addresses and domain name that makes sense to you.
LOCAL nError AS LONG
DIM arSendCC() AS LOCAL EMAILADDRESS
DIM arSendTo() AS LOCAL EMAILADDRESS
DIM arAttachments() AS LOCAL STRING
LOCAL sErrorDescription AS STRING
LOCAL sServerMessage AS STRING
LOCAL sServerResponse AS STRING
LOCAL sMessageBody AS STRING
LOCAL sPlainText AS STRING
LOCAL nReturn AS LONG
LOCAL SMTPCTX AS SMTPCONTROL
DIM arInvalidRecipients() AS LOCAL EMAILADDRESS
DIM arMailServers(0) AS LOCAL ASCIIZ * %MAX_HOSTNAME_LEN
IF DNSMXQuery ("mydomain.com", _ ' Put the domain.top level domain here of your recipient...ie gmail.com
arMailServers(), _
nError, _
sErrorDescription) = %TRUE THEN ' Could loop through all servers until one worked...
' SMTPCTX.nAuthenticate = %TRUE
' SMTPCTX.szUserName = "username"
' SMTPCTX.szUserPassword = "userpassword"
SMTPCTX.szMailServer = arMailServers (0)
SMTPCTX.szSubject = "SMTPSend Test"
SMTPCTX.nPriority = %SMTP_PRIORITY_HIGH
SMTPCTX.nSensitivity = %SMTP_SENSITIVITY_PRIVATE
SMTPCTX.nReturnReceipt = %TRUE
SMTPCTX.nHTMLMessage = %FALSE
SMTPCTX.nIncludePlainText = %TRUE
SMTPCTX.nMaskRecipients = %TRUE
SMTPCTX.szMaskToName = "CRSMTP Test"
SMTPCTX.szMaskAddress = "anybody@somebody.com" ' Your replay email address here
SMTPCTX.szFromName = "FF 3.51" ' Your Name here
SMTPCTX.szFromAddress = "anybody@somebody.com"
SMTPCTX.szXMailer = "CRSMTP"
SMTPCTX.szXMIMEOLE = "CRSMTP OLE Provider"
' Mask Recipients = %FALSE
' DIM arSendTo (0 TO 0)
' arSendTo(0).szName = "Someone's Name"
' arSendTo(0).szAddress = "anybody@somebody.com"
' Mask Recipients = %TRUE
DIM arSendCC (0 TO 0)
arSendCC(0).szName = "Someone's Name"
arSendCC(0).szAddress = "anybody@somebody.com"
' sPlainText = "This is a HTML message."
' sMessageBody = "<html><head><title></title></head><body>This is a <strong>HTML</strong> message.</body></html>"
sMessageBody = "This is a not a HTML message."
' AddAttachment ("c:\path\filename.xxx", arAttachments(), nError, sErrorDescription)
nReturn = SMTPSend (SMTPCTX, _
sMessageBody, _
sPlainText, _
nError, _
sErrorDescription, _
arAttachments(), _
arSendTo(), _
arSendCC(), _
arInvalidRecipients(), _
sServerMessage, _
sServerResponse)
IF nReturn = %TRUE THEN
MSGBOX "Mail sent successfully."
ELSE
MSGBOX "Mail not sent. Error code: " + FORMAT$(nError) + " - " + sErrorDescription
END IF
ELSE
MSGBOX "Domain mail server lookup failed." + $CRLF + $CRLF + FORMAT$(nError) + " - " + sErrorDescription
END IF
Enjoy!
Rick Kelly
Wow.
Awesome Rick - thanks for sharing!
Quote from: TechSupport on December 02, 2011, 10:42:43 AM
Awesome Rick - thanks for sharing!
Thank you :)
I just noticed that I left out one small piece of code I frequently use. In my apps, I usually have a "trace" function that, when activated just logs app flow and important values to a trace file, which in my latest app with this SMTP code, I email to myself after asking the user for a description of their problem and their name and email for reply purposes. One piece of that process is a memory dumper which I use when I'm dealing with structure pointers from api calls. I found it very useful when writing the SMTP code initially. It just takes a memory address and size and dumps it out in a simple text format. Anyways, here is the function code:
FUNCTION MemoryToHex (BYVAL lpMemory AS DWORD, _
BYVAL nSize AS LONG) AS STRING
LOCAL sValues AS STRING
LOCAL sHex AS STRING
LOCAL nSegments AS LONG
LOCAL nIndex AS LONG
LOCAL nSizeLeft AS LONG
LOCAL nOffset AS LONG
LOCAL sChunk AS STRING
LOCAL sPieces AS STRING
LOCAL nChunkIndex AS LONG
LOCAL nTabCount AS LONG
LOCAL sSegment AS STRING
LOCAL sMask AS STRING
LOCAL lpAddress AS DWORD
LOCAL sAddress AS STRING
lpAddress = lpMemory
sSegment = ""
' Values to mask with periods
sMask = CHR$ (0 TO 31) + CHR$ (127 TO 255)
' Get copy of what lpMemory points to
sValues = SPACE$ (nSize)
MEMORY COPY lpMemory, STRPTR (sValues), nSize
' Convert to hex string
CRCAPI_BINARY_TO_HEXSTRING (sValues, sHex)
sHex = UCASE$ (sHex)
' Mask out characters we aren't going to show as raw values
REPLACE ANY sMask WITH STRING$ (LEN(sMask), ".") IN sValues
' Calculate the number of segments we are going to process
nSegments = CEIL (nSize / 16)
nSizeLeft = LEN (sValues)
' Build each decoded line - aaaaaaa xxxxxxxx xxxxxxxx xxxxxxxx bbbbbbbbbbbbbbbbbb
' where aaaa = starting address, xxxxxxx's = hex values and bbbbb's = masked byte values
FOR nIndex = 1 TO nSegments
sAddress = HEX$ (lpAddress,8)
nOffset = ((nIndex * 16) - 16) + 1
sChunk = MID$ (sHex, (nOffset * 2) - 1, IIF (nSizeLeft < 16,nSizeLeft * 2,32))
' Break current hex chunk into 4 byte pieces
sPieces = ""
FOR nChunkIndex = 1 TO LEN(sChunk)
sPieces = sPieces _
+ MID$(sChunk,nChunkIndex,1) _
+ IIF$ (nChunkIndex MOD 8 <> 0,""," ")
NEXT
' Calculate how many tabs we need to keep values aligned
SELECT CASE CEIL (LEN(sChunk) / 8)
CASE 4
nTabCount = 1
CASE 3
nTabCount = 2
CASE 2
nTabCount = 3
CASE ELSE
nTabCount = 4
END SELECT
IF LEN(sChunk) MOD 8 <> 0 THEN
nTabCount = nTabCount + IIF (LEN(sChunk) MOD 8 < 5, 1, 0)
END IF
' Put everything together
sSegment = sSegment _
+ sAddress _
+ " " _
+ RTRIM$(sPieces) _
+ REPEAT$ (nTabCount, $TAB) _
+ MID$ (sValues, nOffset, IIF (nSizeLeft < 16,nSizeLeft,16)) _
+ $CRLF
nSizeLeft = nSizeLeft - 16
lpAddress = lpAddress + 16
NEXT
FUNCTION= sSegment + $CRLF
END FUNCTION
Naturally, I use my CRCAPI DLL posted earlier for the hex conversion but you can modify this to do the conversion however you wish. A sample from a DNS MX call for my crooit.com domain is attached as an example of the results. Just open in something like Notepad - its only a simple text file.
Rick Kelly
Interesting. I recently started the same thing in PHP. There are limitations to the mail functions within it and not all customers have their own mail servers, so I had to make a function to scan the MX records and connect directly. Much quicker and less likely to have ISPs and such store your sent mail since it connects directly to the final destination. http://www.softstack.com/ has some cool apps for this too I had used like their Free SMTP Server before my PHP code was finished to just have it running and use it to email. I wish all email applications just sent mail that way, but with SMTP ports being blocked and spammers sending emails that way it unfortunately may not work too well in the future.
Richard,
I have tried to use your code but am running into several "Undefined Equate" errors. I must
be doing something wrong. Would you perhaps have a sample FF project that demonstrates
how to use your email code? Thanks for sharing.
That file is not compatible neither with my current headers nor with the current PB headers. It uses the old PB includes pre-PB10.
Jose,
Is it easily possible to make this code compatible with PB10 ?
Is there a "SendEmail" function that you know of that is compatible with FF3.6/PB10 ?
There is one, PBSendMapiMail, in Mapi.inc, but uses MAPI, not SMTP.
The attached are FF modules I used in 3.51 with Jose's includes. I think Jose's includes are:
#INCLUDE "WINDNS.INC"
#INCLUDE "WINSOCK2.INC"
#INCLUDE "RAS.INC"
#INCLUDE "OBJIDL.INC"
#INCLUDE "URLMON.INC"
#INCLUDE "WS2TCPIP.INC"
#INCLUDE "IPHLPAPI.INC"
#INCLUDE "wininet.inc"
Rick