Send Email From Microsoft Access The Easy Way

sixHat

Registered User.
Local time
Yesterday, 18:04
Joined
Apr 28, 2012
Messages
46
I just wanted to share a great solution I’ve recently developed in Emailing from Microsoft Access. When I was researching email methods from Access a thought occurred to me that there should be an A.P.I. that would do this the easy way. In fact there are several available… but none that I could find provided VBA code examples (which didn’t surprise me).

All of them did have a web A.P.I. which was all I needed to translate a web request from the other language examples given. In the End I ended up taking Eli the Computer Guy’s recommendation and used the SendGrid API. I’ve found that SendGrid seems to have 24/7 online support which is great for contractors like me who seem to burn the late night hours.

IMPORTANT NOTE: This script has been changed due to SendGrid API changes which broke the former attachment functionality. Justin Steele provided the key to fixing the attachment issues. The solution shown below is my own implementation with her provided attachment fix:



Code:
Sub SendEmail()
    Dim HttpReqURL As String
    Dim eUser As String
    Dim ePass As String
    Dim eTo As String
    Dim eToName As String
    Dim eSubject As String
    Dim eBody As String
    Dim eFrom As String
    Dim multiPartBoundary As String
    Dim outputStream As Object
    Dim binaryStream As Object
    Dim rs As DAO.Recordset
    Dim SQL As String

 
    Const adSaveCreateNotExist = 1
    Const adSaveCreateOverWrite = 2
    Const adTypeBinary = 1
    Const adTypeText = 2
    Const adModeReadWrite = 3

    multiPartBoundary = "123456789abc"
    HttpReqURL = "https://api.sendgrid.com/api/mail.send.json"
    
    eSubject = Me.txtSubject
    eBody = Me.txtMessage
    eFrom = SenderEmail
    eUser = SendGridUser
    ePass = SendGridPass

    ' If Groups List/ Else Contacts List
    If Me.chkGroups <> 0 Then
        SQL = "SELECT * FROM qryContactsInSelectedGroups WHERE ContactType = 'Email'"
    Else
        SQL = "SELECT * FROM qrySelectedContacts WHERE ContactType = 'Email'"
    End If
    Set rs = CurrentDb.OpenRecordset(SQL, dbOpenDynaset, dbSeeChanges)

    If Not (rs.EOF And rs.BOF) Then
        rs.MoveFirst
        Do Until rs.EOF = True
            eTo = rs.Fields("ContactValue").value
            eToName = rs.Fields("FirstName").value & " " & rs.Fields("LastName").value
            
            Set outputStream = CreateObject("adodb.stream")
            outputStream.Type = adTypeText
            outputStream.Mode = adModeReadWrite
            outputStream.Charset = "windows-1252"
            outputStream.Open
        
            AddStreamParam outputStream, multiPartBoundary, "api_user", eUser
            AddStreamParam outputStream, multiPartBoundary, "api_key", ePass
            AddStreamParam outputStream, multiPartBoundary, "to", eTo
            AddStreamParam outputStream, multiPartBoundary, "toname", eToName
            AddStreamParam outputStream, multiPartBoundary, "subject", eSubject
            AddStreamParam outputStream, multiPartBoundary, "text", eBody
            AddStreamParam outputStream, multiPartBoundary, "from", eFrom

            ' Add Attachments
            AddAttachmentsToStream outputStream, multiPartBoundary
            outputStream.WriteText "--" + multiPartBoundary + "--" + vbCrLf
            
            Set binaryStream = CreateObject("ADODB.Stream")
            binaryStream.Mode = 3 'read write
            binaryStream.Type = 1 'adTypeText 'Binary
            binaryStream.Open
        
            ' copy text to binary stream so xmlHttp.send works correctly
            outputStream.Position = 0
            outputStream.CopyTo binaryStream
            outputStream.Close
            
            binaryStream.Position = 0
        
            Dim xmlHttp As Object
            Set xmlHttp = CreateObject("MSXML2.XMLHTTP")
            xmlHttp.Open "POST", HttpReqURL, False
            xmlHttp.setRequestHeader "Content-Type", "multipart/form-data; boundary=" + multiPartBoundary
            xmlHttp.setRequestHeader "Content-Length", Len(binaryStream.Size)
            xmlHttp.send binaryStream.Read(binaryStream.Size)
        
            binaryStream.Close
            rs.MoveNext
        Loop
    End If
    Set rs = Nothing
End Sub

Sub AddStreamParam(stream As Variant, boundary As String, paramName As String, value As String)
    stream.WriteText "--" + boundary + vbCrLf
    stream.WriteText "Content-Disposition: form-data; name=""" + paramName + """" + vbCrLf
    stream.WriteText vbCrLf
    stream.WriteText value + vbCrLf
End Sub

Sub AddFileToStream(stream As Variant, boundary As String, name As String, filePath As String)
    Dim fileBytes As String
    fileBytes = ReadBinaryFile(filePath)

    stream.WriteText "--" + boundary + vbCrLf
    stream.WriteText "Content-Disposition: form-data; name=""files[" + name + "]""; filename=""" + name + """" + vbCrLf
    stream.WriteText "Content-Type: application/octet-stream" + vbCrLf
    stream.WriteText vbCrLf
    stream.WriteText fileBytes + vbCrLf
End Sub

Sub AddAttachmentsToStream(stream As Variant, boundary As String)
    Dim rs As DAO.Recordset
    Dim rsAttach As DAO.Recordset
    Dim SQL As String
    Dim currentAttachment As String
    Dim strAttachments As String
    Dim fileName As String

    SQL = "SELECT * FROM tblMessageAttachments WHERE [MessageID] = " & MessageID
    Set rsAttach = CurrentDb.OpenRecordset(SQL, dbOpenDynaset, dbSeeChanges)

    If Not (rsAttach.EOF And rsAttach.BOF) Then
        rsAttach.MoveFirst
        Do Until rsAttach.EOF = True
            ' Set Current Attachment
            fileName = rsAttach.Fields("AttachmentName").value
            currentAttachment = rsAttach.Fields("AttachmentLocation").value & fileName
            'Debug.Print currentAttachment
            
            ' Add Attachement to outputStream
            AddFileToStream stream, boundary, fileName, currentAttachment
            rsAttach.MoveNext
        Loop
    End If
End Sub

Function ReadBinaryFile(strPath)
    Dim oFSO: Set oFSO = CreateObject("Scripting.FileSystemObject")
    Dim oFile: Set oFile = oFSO.GetFile(strPath)

    If IsNull(oFile) Then MsgBox ("File not found: " & strPath): Exit Function

    With oFile.OpenAsTextStream()
        ReadBinaryFile = .Read(oFile.Size)
        .Close
    End With
End Function
 
Last edited:
So the advantage of all that stuff over a plain Outlook or some other MAPI client or a CDO solution is what exactly?
 
Hmmm ... if you want to extoll the virtues of something then a link on its own does not do the trick, me thinks. But no skin off my nose ...
 
Why don't you use

DoCmd.SendObject(ObjectType, ObjectName, OutputFormat, To, Cc, Bcc, Subject, MessageText, EditMessage, TemplateFile)

Much simpler isn't it???
 
Yes the Docmd.SendObject is much easier programatically... but it has limitations. Namely for me is the fact that you can only send one attachment and even then you are limited to the type of attachment... so thus it's hardly ever a method I could actually use.

Here are a list of limitations provided by FMS
While the SendObject command is useful for sending an object or short message, it has several significant limitations:

Messages must be 255 characters or less
Messages are plain text and cannot be HTML format
Cannot attach multiple files (limited to one attachment)
Cannot attach a file on disk
Cannot filter the data source or report to just the data you need to send
Cannot specify the FROM address
Cannot specify settings such as priority, sensitivity, and read receipt
MAPI security dialog box prompts the user for each email to verify it is okay to send
Doesn't always work with email programs if it's not Outlook, Outlook Express, or Exchange

So when I do need multi attachment functionality my usual method that I used was CDO... which was great and while it required more lines of code than the Docmd.SendObject method it was pretty simple to use as well. But it had the limitation of being mail server dependent (If your customer even has one because some of my small business customers do not). You need a minimum of the companies mail server i.p. address... and sometimes full credentials depending on how I.T. has it configured.

When your an outside contractor like I am, it's not always easy obtaining this information as I.T. understandably doesn't just give this information out to everyone.

I speak more about the reasons why I used SendGrid here: https://www.linkedin.com/pulse/email-from-microsoft-access-easy-way-anthony-griggs

In the end... I really feel it's a great option that is simple to use, simple to implement and gives Access developers great flexability without all of the limitations of some of the others methods. Although it may not fit every situation... I think it will fit most.
 
My pleasure... glad it worked for your situation
 
@sixHat, et al, thanks for this solution as I do prefer to use sendgrid for transactional emails.

HOWEVER... I just can't seem to get attachments to work. The attachment name and path are sent to the SendGrid http API, but how is SendGrid to actually access the file?

I see that you have fso and file objects declared in the sub, but they aren't utilized. Is the code missing something?

Many thanks for anybody that could help me out. Thanks!
 
My reference to the FileSystemObjects Library was actually from another module where I gathered the file location of attachments through an open file dialog. While you may use the same method I left it out for a simplified version of what was needed.

The SendGrid API gathers the attachment by the file path you provide as provided in my strAttachment demo which was actually a demonstration of 3 different attachments being sent out. For clarification each attachment should be broken down like this:

strAttachments ="&files[FileName]=FilePath"
You should note that "FilePath" is a full file path with the included file name. For each file that you are attaching you should concatenate that same format into it.

I really feel that if you work on getting the file path formatting down you should get it. Of course it should be stated that you should have permissions to access the directories that the attachments" are located.

If you are confident that you are following the formatting correctly and attachments are not being sent out then you should look into the SendGrid Email Log... maybe something within there system prevented the files from being attached like file size or file type etc. If you do not see anything there you can always reach out to SendGrid which I've done a time or two myself. They have friendly 24/7 support and can answer any questions you may have. You should be able to reference an email sent from your log and then ask them if they've seen any record of attachments with that email.

Hope that helps! Please let me know how it went.
 
Also do a Debug.Print of your final strAttachment and post it here to see what it looks like
 
Thanks for your reply. This is my debug.print (w/ sensitive info x'd out)

Code:
https://api.sendgrid.com/api/mail.send.xml?api_user=xxx&api_key=xxx&to=joshua@xxx.com&toname=xxx&subject=Easy Way To Email From Access&text=Send Emails Via The SendGrid API Directly From Access!&from=manager@xxx.com&files[scan.pdf]=C:\Users\jrailton\Desktop\scan.pdf

The email goes out and is received with an attachment, but the attachment is empty.

The scan.pdf file on my desktop is 50k, so I know size limitations aren't the issue.

I'll checkout the SendGrid logs.

Thanks!
 
Something else you may try (which should not be necessary but...) is zipping the attachment... at least just to see what happens. I think I remember having this happen when emailing that is my .pdf getting corrupted somehow during the process.

Also have you tried different file types? If so did that make any difference?
 
It should be noted that I can not remember if my pdf corruption happened with SendGrid or some other emailing method... but I do remember having this same issue once.
 
I've tried several different file locations and different file names and PDF as well as JPG files... all with the same result. It's as if I'm sending a shell of a file, but with no content.

We send out 200-300 transactional emails per day and we've been using CDO to send them via zoho smtp server through our zoho email account.

I was looking into using SendGrid to avoid our bad reputation (yahoo and aol have been blocking our customers from receiving transactional emails, and some others).

I think I'll go to just using the SendGrid smtp via CDO for now. I just kind of liked the idea of dealing with this via port 80 and that's why I was so happy to see your implementation.

SendGrid support chat, etc. seems to be limited to paying customers. I'm on the free tier at this point. I think I will revisit this at some point.

Much obliged for your time. Thanks for sharing and helping.
 
I've had the same problem. I'm trying to attach a PDF file. The email sends but with a file that looks like a "1KB PDF file" but it cannot be opened as a PDF. I downloaded it and changed the extension to .txt and the file shows the filepath only. Please let me know if you have any other ideas to get the file to attach. I tried everything you mentioned on this post.
 
I'll take a look into this further and see what I can dig up.
 
@enjoysoshua AND all the others that were following this post so as to a resolution. I spoke to SendGrid support and explained the situation. Basically there reply was: "We make changes to our API all the time which is why we always recommend using our libraries as we keep these up to date with the changes".

That being said Justin Steele came up with the resolution to date. You can see the resolution and post here: http://stackoverflow.com/questions/35423101/sendgrid-attachments-are-empty-or-corrupt-using-api-vba

I went round and round trying to resolve this and finally reached out as my multiple attempts were falling short as I am not that familiar with form headers and the few examples I could find were not meant for this type of use. Justin's very hard work paid off for us all! So please be sure to give Justin an Upvote if you get a chance!
 

Users who are viewing this thread

Back
Top Bottom