Skip to content

Email attachments in SES V2 : incorrect inline documentation and MIME implementation

0

Hello all,

I am trying to use the SES API v2 to send emails with inline images, and I have observed some problems in the official documentation and the current implementation.

See this example:

{
    "FromEmailAddress": "sender@domain",
    "Destination": {
        "ToAddresses": [
            "recipient@domain"
        ]
    },
    "Content": {
        "Simple": {
            "Subject": {
                "Data": "Email with attachment"
            },
            "Body": {
                "Html": {
                    "Data": "<html><body>Our logo:<br><img src=\"cid:logo123\" alt=\"Company Logo\"></body></html>"
                }
            },
            "Attachments": [
                {
                    "RawContent": "<base64-encoded-content>",
                    "ContentDisposition": "INLINE",
                    "FileName": "logo.png",
                    "ContentId": "logo123"
                }
            ]
        }
    }
}

1) If you insert a base64 content for an image in this sample, it will not work until you explicitly add "ContentTransferEncoding": "BASE64".

This question has already been asked in a previous question. You should update your sample and the documentation to set this value. Or update the API to assume BASE64 when not set.

Example base64 PNG image:

iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII=

2) The email is sent with a "multipart/mixed" content-type with the inline content in it, which is wrong based on the RFC.

Most online mail will fix this by assuming a "multipart/related", but some more stricts (such as thunderbird desktop) will reject this format. The correct format will work in all email clients (outlook desktop, outlook web, gmail, thunderbird).

References:

Wrong format:

From: sender@domain
To: recipient@domain
Subject: Email with attachment
MIME-Version: 1.0
Content-Type: multipart/mixed; 
	boundary="----=_Part_49962_7317198.1758782950589"
Date: Thu, 25 Sep 2025 06:49:10 +0000

------=_Part_49962_7317198.1758782950589
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 7bit

<html><body>Our logo:<br><img src="cid:logo123" alt="Company Logo"></body></html>
------=_Part_49962_7317198.1758782950589
Content-Type: image/png; name=logo.png
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename=logo.png
Content-ID: <logo123>

[skipped]
------=_Part_49962_7317198.1758782950589--

Correct format:

From: sender@domain
To: recipient@domain
Subject: Email with attachment
MIME-Version: 1.0
Content-Type: multipart/related; 
	boundary="----=_Part_49962_7317198.1758782950589"

------=_Part_49962_7317198.1758782950589
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 7bit

<html><body>Our logo:<br><img src="cid:logo123" alt="Company Logo"></body></html>
------=_Part_49962_7317198.1758782950589
Content-Type: image/png; name=logo.png
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename=logo.png
Content-ID: <logo123>

[skipped]
------=_Part_49962_7317198.1758782950589--

3) Mixed text/plain and text/html are not correctly formatted

If you set both text/plain and text/html format, it will be written as multipart/alternative block with the inline images as part of the root multipart/mixed section instead of the missing multipart/related section.

{
    "FromEmailAddress": "sender@domain",
    "Destination": {
        "ToAddresses": [
            "recipient@domain"
        ]
    },
    "Content": {
        "Simple": {
            "Subject": {
                "Data": "Email with attachment"
            },
            "Body": {
                "Text": {
                    "Data": "Our logo: [Company Logo]."
                },
                "Html": {
                    "Data": "<html><body>Our logo:<br><img src=\"cid:logo123@inline\" alt=\"Company Logo\">.</body></html>"
                }
            },
            "Attachments": [
                {
                    "RawContent": "[skipped]",
                    "ContentDisposition": "INLINE",
                    "FileName": "logo.png",
                    "ContentId": "logo123@inline",
                    "ContentTransferEncoding": "BASE64"
                }
            ]
        }
    }
}

Wrong:

From: sender@domain
To: recipient@domain
Subject: Email with attachment
MIME-Version: 1.0
Content-Type: multipart/mixed; 
	boundary="----=_Part_361090_2131044978.1758785636357"

------=_Part_361090_2131044978.1758785636357
Content-Type: multipart/alternative; 
	boundary="----=_Part_361091_322425076.1758785636357"

------=_Part_361091_322425076.1758785636357
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit

Our logo: [Company Logo].
------=_Part_361091_322425076.1758785636357
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 7bit

<html><body>Our logo:<br><img src="cid:logo123@inline" alt="Company Logo">.</body></html>
------=_Part_361091_322425076.1758785636357--

------=_Part_361090_2131044978.1758785636357
Content-Type: image/png; name=logo.png
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename=logo.png
Content-ID: <logo123@inline>

[skipped]
------=_Part_361090_2131044978.1758785636357--

More generally, the wrong assumption is that inline disposition should be considered the same as attachment disposition when building a MIME body. It's not the case: inline elements should be part of a multipart/related section with the text/html content first.

Here are the correct MIME rules for each case:

  • If both text/plain and text/html are present, they should be encapsulated in a multipart/alternative block, with the most important part last. The API handles this case correctly.
  • If attachment disposition are present, a root multipart/mixed section should be added. The API handles this case correctly.
  • If inline disposition are present, they should be next to the text/html section, encapsulated in a multipart/related section.

The MIME structure when all case are occurring (1+ attachment, 1+ inline, text+html) should be:

  • multipart/mixed
    • multipart/alternative
      • text/plain
      • multipart/related
        • text/html
        • image/png (disposition: inline)
        • image/png (disposition: inline)
        • ...
    • application/pdf (disposition: attachment)
    • ...

If no text/plain version are present (1+ attachment, 1+ inline, html only):

  • multipart/mixed
    • multipart/related
      • text/html
      • image/png (disposition: inline)
      • image/png (disposition: inline)
    • application/pdf (disposition: attachment)
    • ...

If no attachment are present (1+ inline, html only):

  • multipart/related
    • text/html
    • image/png (disposition: inline)
    • image/png (disposition: inline)

No attachments at all (html + text):

  • multipart/alternative
    • text/plain
    • text/html
2 Answers
0

You've identified several important issues with Amazon SES V2 API's handling of email attachments and inline images:

  1. Missing ContentTransferEncoding parameter: You're correct that when using base64-encoded content with the SES V2 API, you need to explicitly specify "ContentTransferEncoding": "BASE64" in the attachment configuration. Without this parameter, the attachment won't render correctly. This should either be documented clearly or the API should assume BASE64 encoding when not specified.

  2. Incorrect MIME structure for inline images: The current implementation uses a multipart/mixed content type for emails with inline images, which is technically incorrect according to email standards. For inline images referenced in HTML content, the proper MIME structure should use multipart/related instead. This incorrect implementation can cause rendering issues in email clients that strictly follow MIME standards, such as Thunderbird.

  3. Improper handling of mixed content types: When combining text/plain, text/html, and inline images, the MIME structure is not properly organized. Inline images should be part of a multipart/related section alongside the HTML content, not directly under the root multipart/mixed section.

The correct MIME structure for emails with both text and HTML versions plus inline images should follow this hierarchy:

  • multipart/mixed (if attachments are present)
  • multipart/alternative
  • text/plain
  • multipart/related
  • text/html
  • inline images (with Content-Disposition: inline)
  • regular attachments (with Content-Disposition: attachment)

Amazon SES introduced attachment support in the V2 API's "Simple" send type in April 2025, making it easier to send emails with attachments without needing to manually construct complex MIME messages. However, these implementation issues can affect how emails render in certain email clients, particularly those that strictly adhere to email standards.

For now, if you need precise control over the MIME structure, you might need to use the raw email sending capabilities of SES instead of the Simple send type, though this requires more complex code to properly construct the email.
Sources
How can I use SES V2 API send email with attachment? | AWS re:Post
Working with email attachments in SES - Amazon Simple Email Service
Sending raw email using the Amazon SES API v2 - Amazon Simple Email Service
Sending raw email using the Amazon SES API - Amazon Simple Email Service
Amazon SES now offers attachments in sending APIs - AWS

answered a year ago

  • The suggested hierarchy does not highlight the different nesting levels (regular attachments are on the same level as inline images). Your answer should correctly show the hierarchy of the MIME sections. I will not accept this anwser as-is because of this.

0

Hello,

Thank you for addressing this issue with our current documentation within SES. While we are still verifying these changes, we have gone ahead and made the decision to update example in the public documentation to the following example.

{
    "FromEmailAddress": "sender@example.com",
    "Destination": {
        "ToAddresses": [
            "recipient@example.com"
        ]
    },
    "Content": {
        "Simple": {
            "Subject": {
                "Data": "Email with attachment"
            },
            "Body": {
                "Html": {
                    "Data": "<html><body>Our logo:<br><img src=\"cid:logo123\" alt=\"Company Logo\"></body></html>"
                }
            },
            "Attachments": [
                {
                    "RawContent": "<base64-encoded-content>",
                    "ContentDisposition": "INLINE",
                    "FileName": "logo.png",
                    "ContentId": "logo123",
                    "ContentTransferEncoding": "BASE64"
                }
            ]
        }
    }
}

Using the above example will allow you to successfully send attachments via Thunderbird without issue.

Please let us know if you have any questions.

AWS

answered a year ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.