SMTP Support

Started by Richard Kelly, December 02, 2011, 05:35:07 AM

Previous topic - Next topic

Richard Kelly

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

Jim Dunn

3.14159265358979323846264338327950
"Ok, yes... I like pie... um, I meant, pi."

Paul Squires

Awesome Rick - thanks for sharing!
Paul Squires
PlanetSquires Software

Richard Kelly

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

Roger Garstang

#4
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.

Martin Francom

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.

José Roca

That file is not compatible neither with my current headers nor with the current PB headers. It uses the old PB includes pre-PB10.

Martin Francom

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 ?

José Roca

There is one, PBSendMapiMail, in Mapi.inc, but uses MAPI, not SMTP.

Richard Kelly

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