【问题标题】:SendGrid Attachments Are Empty or Corrupt Using API (VBA)使用 API (VBA) 的 SendGrid 附件为空或损坏
【发布时间】:2016-02-16 03:23:28
【问题描述】:

这似乎是 SendGrid Web API 和电子邮件附件的一个常见问题。我在网上找到了很多很多帖子,所有人都遇到了同样的问题……但似乎没有一个可以得到解决方案的回答。 SendGrid 自己的预设回复是使用他们的库之一......但问题仍然是当您使用没有库的语言时如何附加文件。

我已经尝试就这个问题亲自联系 SendGrid 支持...甚至提出支付支持以获得答案,但他们认为我要求进行“代码审查”,而我不是。问题很简单:将附件上传到 SendGrid Web API 需要什么。

我以前只是在建议的 API 格式中提供文件位置,如下所示:Previous Example of Posting to SendGrid Using VBA,这对我自己和其他几个人来说似乎工作了一段时间......但最近发生了一些变化。提供简单的文件路径似乎不再起作用。那么我现在需要做什么呢?我应该对文件进行编码吗?如果是这样,我应该使用 base64 什么编码?我和其他许多人将非常感谢这方面的任何帮助!

这是我的 base64 尝试,但它与我之前的文件路径尝试存在相同的问题,即附件显示在电子邮件中...但无法打开。

Private Sub SendEmail()
    Dim rs As DAO.Recordset
    Dim SQL As String
    Dim byteData() As Byte
    Dim xmlhttp As Object
    Dim eTo As String
    Dim eFrom As String
    Dim eBody As String
    Dim eSubject As String
    Dim eToName As String
    Dim HttpReq As String
    Dim ePass As String
    Dim eUser As String
    Dim strXML As String
    Dim strAttachments As String
    Dim strBase64 As String



    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 the Server URL to the form input
            HttpReq = "https://api.sendgrid.com/api/mail.send.xml?" _
            & "api_user=" & eUser _
            & "&api_key=" & ePass _
            & "&to=" & eTo _
            & "&toname=" & eToName _
            & "&subject=" & eSubject _
            & "&text=" & eBody _
            & "&from=" & eFrom _
            & GetAttachments()
            ' files[file1.jpg]=file1.jpg&files[file2.pdf]=file2.pdf
            Set xmlhttp = CreateObject("MSXML2.XMLHTTP")
           ' adoStream.Position = 0
            xmlhttp.Open "POST", HttpReq, False
            xmlhttp.send

            byteData = xmlhttp.responseBody

            Set xmlhttp = Nothing
            strXML = StrConv(byteData, vbUnicode)
            Call EmailResponse(strXML, rs.Fields("ContactID").Value)
            Debug.Print strXML
            rs.MoveNext
        Loop
    End If
    Set rs = Nothing
End Sub

    Private Function GetAttachments() As String
    Dim rs As DAO.Recordset
    Dim SQL As String
    Dim currentAttachment As String
    Dim strAttachments As String
    Dim Encoded64 As String

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

    If Not (rs.EOF And rs.BOF) Then
        rs.MoveFirst
        Do Until rs.EOF = True
            ' Set Current Attachment
            currentAttachment = rs.Fields("AttachmentLocation").Value & rs.Fields("AttachmentName").Value
            Encoded64 = EncodeFile(currentAttachment)
            strAttachments = strAttachments & "&files" & Chr(91) & rs.Fields("AttachmentName").Value & Chr(93) & "=" & Encoded64 'currentAttachment
            'strAttachments = strAttachments & Encoded64
           ' Debug.Print strAttachments

            rs.MoveNext
        Loop
        Debug.Print strAttachments
        GetAttachments = strAttachments
    End If

End Function

Private Function EncodeFile(text As String) As String
  Dim arrData() As Byte
  arrData = StrConv(text, vbFromUnicode)

  Dim objXML As MSXML2.DOMDocument
  Dim objNode As MSXML2.IXMLDOMElement

  Set objXML = New MSXML2.DOMDocument
  Set objNode = objXML.createElement("b64")

  objNode.DataType = "bin.base64"
  objNode.nodeTypedValue = arrData
  EncodeFile = Replace(objNode.text, vbLf, "")

  Set objNode = Nothing
  Set objXML = Nothing

End Function

【问题讨论】:

  • 您在使用任何本地库吗?如果没有,请考虑如何将本地文件上传/发送到 sendgrid 网络服务器?即使您生成多部分电子邮件内容,问题是 SendGrid Web 请求是否会理解它。你最好寻求他们的帮助!
  • 只是出于好奇,outlook 不是发送邮件的选项吗?
  • 你为什么要对文件进行base64编码?您需要根据SendGrid docs“文件内容必须是多部分 HTTP POST 的一部分”将其作为多部分帖子的一部分发送。您需要发送带有 content-typemultipart/form-data 的 POST
  • @bwest...这就是我的想法,我首先尝试过...但它似乎不起作用所以我想知道我是否使用了正确的编码。

标签: ms-access vba sendgrid


【解决方案1】:

此代码有一些附加代码和逻辑来附加多个附件:

Option Explicit

Sub SendEmailUsingSendGrid()
    Dim HttpReqURL As String: HttpReqURL = "https://api.sendgrid.com/api/mail.send.json"

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

    Dim YOUR_SG_CREDS_USERNAME As String
    YOUR_SG_CREDS_USERNAME = "username"

    Dim YOUR_SG_CREDS_PASSWORD As String
    YOUR_SG_CREDS_PASSWORD = "password"

    Dim multiPartUploadBoundary As String
    multiPartUploadBoundary = "123456789abc"

    Dim eTo As String
    eTo = "to@example.com"

    Dim eToName As String
    eToName = "To Name"

    Dim eSubject As String
    eSubject = "My Subject"

    Dim eBody As String
    eBody = "This is a test!"

    Dim eFrom As String
    eFrom = "from@example.com"

    Dim outputStream As Object
    Set outputStream = CreateObject("adodb.stream")
    outputStream.Type = adTypeText
    outputStream.Mode = adModeReadWrite
    outputStream.charset = "windows-1252"
    outputStream.Open

    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "api_user", YOUR_SG_CREDS_USERNAME
    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "api_key", YOUR_SG_CREDS_PASSWORD
    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "to", eTo
    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "toname", eToName
    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "subject", eSubject
    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "text", eBody
    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "from", eFrom

    Dim filesToAttach As New Collection
    filesToAttach.Add "C:\temp\test.png"
    filesToAttach.Add "C:\temp\test2.jpg"

    AddMultipleFilesToStream outputStream, multiPartUploadBoundary, filesToAttach

    outputStream.WriteText "--" + multiPartUploadBoundary + "--" + vbCrLf

    Dim binaryStream As Object
    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=" + multiPartUploadBoundary
    xmlHttp.setRequestHeader "Content-Length", Len(binaryStream.Size)
    xmlHttp.send binaryStream.Read(binaryStream.Size)

    binaryStream.Close
End Sub

Sub AddParameterAndValueToStream(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 AddMultipleFilesToStream(stream As Variant, boundary As String, filePaths As Collection)
    Dim fileCount As Integer
    fileCount = filePaths.Count

    For n = 1 To fileCount
        Dim fileName As String
        Dim filePath As String

        filePath = filePaths(n)
        fileName = Right(filePath, Len(filePath) - InStrRev(filePath, "\"))

        AddFileToStream stream, boundary, fileName, filePath
    Next n
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

【讨论】:

  • 再次感谢贾斯汀...我还没有时间剖析这个,但非常感谢您的回复!
  • 当我发现这个时,我注意到这几乎是我已经尝试过的。您是否对此进行了测试?...因为我无法让它在我的最终工作,而且我可能遗漏了一些小东西。
  • 好的...没关系!我是新的,这是一件小事,实际上是我的错。对于我之前的实验,我已经移动了这条线: binaryStream.Position = 0 并且它不合适。将其重新设置解决了问题,并且效果很好!再次感谢您对 Justin 的帮助!
【解决方案2】:

来了!

Option Explicit

Sub SendEmailUsingSendGrid()
    Dim attachmentPath As String: attachmentPath = "C:\temp\test.png"
    Dim HttpReqURL As String: HttpReqURL = "https://api.sendgrid.com/api/mail.send.json"

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

    Dim YOUR_SG_CREDS_USERNAME As String
    YOUR_SG_CREDS_USERNAME = "username"

    Dim YOUR_SG_CREDS_PASSWORD As String
    YOUR_SG_CREDS_PASSWORD = "password"

    Dim multiPartUploadBoundary As String
    multiPartUploadBoundary = "123456789abc"

    Dim eTo As String
    eTo = "to@example.com"

    Dim eToName As String
    eToName = "To Name"

    Dim eSubject As String
    eSubject = "My Subject"

    Dim eBody As String
    eBody = "This is a test!"

    Dim eFrom As String
    eFrom = "from@example.com"

    Dim outputStream As Object
    Set outputStream = CreateObject("adodb.stream")
    outputStream.Type = adTypeText
    outputStream.Mode = adModeReadWrite
    outputStream.charset = "windows-1252"
    outputStream.Open

    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "api_user", YOUR_SG_CREDS_USERNAME
    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "api_key", YOUR_SG_CREDS_PASSWORD
    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "to", eTo
    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "toname", eToName
    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "subject", eSubject
    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "text", eBody
    AddParameterAndValueToStream outputStream, multiPartUploadBoundary, "from", eFrom
    AddFileToStream outputStream, multiPartUploadBoundary, "test.png", "C:\temp\test.png"
    outputStream.WriteText "--" + multiPartUploadBoundary + "--" + vbCrLf

    Dim binaryStream As Object
    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=" + multiPartUploadBoundary
    xmlHttp.setRequestHeader "Content-Length", Len(binaryStream.Size)
    xmlHttp.send binaryStream.Read(binaryStream.Size)

    binaryStream.Close
End Sub

Sub AddParameterAndValueToStream(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

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

【讨论】:

  • 绝对漂亮!!非常感谢,这真的把它带回家了……我不熟悉表格标题,所以我的多次尝试都错过了。你知道一个很好的资源,以便我可以进一步研究并获得它的全部范围吗?我似乎真的找不到分解的例子。另请注意,我将转发此内容,因为许多其他人也试图找到解决此问题的方法。
  • 老实说,我不记得我开始时与多部分 POST 相关的几篇文章(我会尝试重新定位它们)。我在查看消息并“逆向工程”工作消息之前发现了其中的大部分内容,直到我使用 C# 得到了一些工作。一旦我有 C# 工作,我开始尝试使用 VBA 发出相同的消息。如果没有看到实际文件通过网络发送的十六进制数据,我永远不会想到这一点(感谢提琴手!)。然后我必须弄清楚如何让二进制文件真正完整发送,因为如果它是从 VBA 作为文本发送的,它会被切断。
  • 这很好用......我遇到的唯一问题是在哪里分隔附件以便添加多个附件......它不像我最初想象的那样干净利落,因为我尝试过分割它在附件循环中的几个地方都没有成功?您是否已经完成了多附件电子邮件?
  • 我会试一试多附件。
  • 我添加了一个多附件答案。
【解决方案3】:

请看我的“在这里!”回答。我仅出于历史原因将这个答案留在这里。

试试这样的:

' Set the Server URL to the form input
HttpReqURL = "https://api.sendgrid.com/api/mail.send.json"

boundary = "----------------------------123456789abc"

Set xmlhttp = CreateObject("MSXML2.XMLHTTP")
xmlhttp.Open "POST", HttpReqURL, False
xmlhttp.setRequestHeader "Content-Type", "multipart/form-data; boundary=" + boundary 

dataToSend = "--" + boundary + vbCrLf
dataToSend = dataToSend + "Content-Disposition: form-data; name=""api_user""" + vbCrLf
dataToSend = dataToSend + vbCrLf
dataToSend = dataToSend + YOUR_API_USER + vbCrLf

dataToSend = dataToSend + "--" + boundary + vbCrLf    
dataToSend = dataToSend + "Content-Disposition: form-data; name=""api_key""" + vbCrLf
dataToSend = dataToSend + vbCrLf
dataToSend = dataToSend + YOUR_API_KEY + vbCrLf

dataToSend = dataToSend + "--" + boundary + vbCrLf            
dataToSend = dataToSend + "Content-Disposition: form-data; name=""to""" + vbCrLf
dataToSend = dataToSend + vbCrLf
dataToSend = dataToSend + eTo + vbCrLf

dataToSend = dataToSend + "--" + boundary + vbCrLf
dataToSend = dataToSend + "Content-Disposition: form-data; name=""toname""" + vbCrLf
dataToSend = dataToSend + vbCrLf
dataToSend = dataToSend + vbCrLf
dataToSend = dataToSend + eToName + vbCrLf

dataToSend = dataToSend + "--" + boundary + vbCrLf
dataToSend = dataToSend + "Content-Disposition: form-data; name=""subject""" + vbCrLf
dataToSend = dataToSend + vbCrLf
dataToSend = dataToSend + eSubject + vbCrLf

dataToSend = dataToSend + "--" + boundary + vbCrLf
dataToSend = dataToSend + "Content-Disposition: form-data; name=""text""" + vbCrLf
dataToSend = dataToSend + vbCrLf
dataToSend = dataToSend + eBody + vbCrLf

dataToSend = dataToSend + "--" + boundary + vbCrLf
dataToSend = dataToSend + "Content-Disposition: form-data; name=""from""" + vbCrLf
dataToSend = dataToSend + vbCrLf
dataToSend = dataToSend + eFrom + vbCrLf

dataToSend = dataToSend + "--" + boundary + vbCrLf
dataToSend = dataToSend + "Content-Disposition: form-data; name=""files[1]""; filename=""myPDF.pdf""" + vbCrLf

dataToSend = dataToSend + vbCrLf
dataToSend = dataToSend + "Content-Type: application/octet-stream" + vbCrLf
dataToSend = dataToSend + vbCrLf

dataToSend = dataToSend + BASE64ENCODEDFILE + vbCrLf
dataToSend = dataToSend + "--" + boundary + "--" + vbCrLf

xmlhttp.send dataToSend

【讨论】:

  • 感谢您的回复,但我不相信您可以从 VBA 发布 JSON ......或者至少据我所知,这样做非常困难,因为它不是故意的。我无论如何都尝试了你的帖子......但不幸的是它是不行的。
  • @AnthonyGriggs json vs xml 不是数据提交的形式(它作为 multipart/form-data 提交)它必须与响应的形式有关。在这种情况下,您可以/应该可以互换使用 json 或 xml。
  • @anthony 我更新了我的答案。我发现在内容配置行和值之间有一个空行并且最后一个边界以“--”结尾是至关重要的
  • 谢谢...我一定会测试一下。我正计划编写一个 .Net 插件来弥补,因为我已经尝试了 100 种不同的多部分/表单数据转换方式并且它们可以工作......但附件文件无法正常工作。我希望这能奏效,因为它可以为我节省大量时间,并且更容易与 Access 集成!
  • 下周我将自己开发一个 vba 解决方案。昨晚我学到了更多的细微差别。
猜你喜欢
  • 1970-01-01
  • 2018-12-19
  • 1970-01-01
  • 2014-11-04
  • 2022-08-18
  • 2011-10-28
  • 2022-01-14
  • 2022-10-18
  • 2014-05-09
相关资源
最近更新 更多