sendgrid / sendgrid-python

The Official Twilio SendGrid Python API Library
https://sendgrid.com
MIT License
1.53k stars 711 forks source link

Error sending email with attachment #934

Closed Arbitrage0 closed 4 years ago

Arbitrage0 commented 4 years ago

Issue Summary

I'm trying to send an email with an attachment, but I get errors saying that (depending on the implementation) either the FileName is not JSON serializable (implementation 1), or that the file content and file name are missing, even though they are not (implementation 2).

Steps to Reproduce

  1. Run the code below with an attachment.

Code Snippet

from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, From, To, Subject, Bcc, Attachment, Content, MimeType, FileContent, FileType, FileName, Disposition, ContentId
from python_http_client import exceptions
import os
import base64

sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))

message = Mail()

message.from_email=From('test@example.com', 'Example')
message.to=To('another@example.com')
message.subject=Subject('Subject')
message.bcc=Bcc('yetanother@example.com')
message.content = Content(MimeType.html, '<strong>Some content</strong>')

file_path = 'file.pdf'
with open(file_path, 'rb') as f:
    data = f.read()
    f.close()
encoded = base64.b64encode(data).decode()

#Impementation 1
message.attachment = Attachment(FileContent(encoded),
  FileType('application/pdf'),
  FileName('file.pdf'),
  Disposition('attachment'),
  ContentId('File'))

#Implementation 2
attachment = Attachment()
attachment.content = encoded
attachment.type = "application/pdf"
attachment.filename = "file.pdf"
attachment.disposition = "attachment"
attachment.content_id = "File"   
message.add_attachment(attachment)

try: 
  response = sg.send(message=message)
  print(response)
except exceptions.BadRequestsError as e:
  print(e.body)

Exception/Log

#Implementation 1 Logs: 

Traceback (most recent call last):
  File "main.py", line 39, in <module>
    response = sg.send(message=message)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/sendgrid/base_interface.py", line 62, in send
    return self.client.mail.send.post(request_body=message)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/python_http_client/client.py", line 257, in http_request
    data = json.dumps(request_body).encode('utf-8')
  File "/usr/lib/python3.8/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "/usr/lib/python3.8/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python3.8/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib/python3.8/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type FileName is not JSON serializable

#Implementation 2 Logs: 
b'{
   "errors":[
      {
         "message":"The attachment content is required.",
         "field":"attachments.0.content",
         "help":"http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#message.attachments.content"
      },
      {
         "message":"The attachment filename parameter is required.",
         "field":"attachments.0.filename",
         "help":"http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#message.attachments.filename"
      }
   ]
}'

Technical details:

thinkingserious commented 4 years ago

Hello @Arbitrage0,

Could you please post the request body? Thanks!

With best regards,

Elmer

Arbitrage0 commented 4 years ago

Hi Elmer, thanks for having a look. As I mentioned, getting the request body in JSON using implementation 1 doesn't work because it yields the same error (i.e. FileName and FileType are not JSON serializable). Using implementation 2, the request body is missing the attachment content, type and filename even though I've declared them:

{
    "attachments": [
        {
            "content_id": "File",
            "disposition": "attachment"
        }
    ],
    "content": [
        {
            "type": "text/html",
            "value": "<strong>Some content</strong>"
        }
    ],
    "from": {
        "email": "test@example.com",
        "name": "Example"
    },
    "personalizations": [
        {
            "bcc": [
                {
                    "email": "yetanother@example.com"
                }
            ],
            "to": [
                {
                    "email": "another@example.com"
                }
            ]
        }
    ],
    "subject": "Subject"
}
childish-sambino commented 4 years ago

For implementation 1, the param order to init the Attachment is incorrect (FileName and FileType are swapped).

    def __init__(
            self,
            file_content=None,
            file_name=None,
            file_type=None,
            disposition=None,
            content_id=None):

For implementation 2, the property names are incorrect. Should be:

attachment.file_content = encoded
attachment.file_type = "application/pdf"
attachment.file_name = "file.pdf"
Arbitrage0 commented 4 years ago

It works now thanks, but you should know that the full example in the Quickstart (which I based my code on) does neither of those things and is therefore incredibly unhelpful. (see Implementation 1, Implementation 2).

childish-sambino commented 4 years ago

Ah, I see. Thanks for the PR!