Text attachments broken in some clients with HTTP injection

Hi all,

We noticed that any `text/*` attachment submitted via the HTTP injection endpoint is build as:

Content-Type: text/plain;
	charset="utf-8";
	name="test.csv"
Content-Transfer-Encoding: quoted-printable

This, sadly, breaks in a lot of clients I tested this with. If I create an MIME with that same file, but as base64:

Content-Type: text/plain; name=test.csv
Content-Transfer-Encoding: base64
Content-Disposition: attachment; name=test.csv; filename=test.csv

It keeps the content disposition + uses base64 encoding. This was generated via Symfony.

With the first one, it ends up as text, appended to the body in e.g. Apple Mail. In the Migadu Webmail, it doesn’t show up at all.

The base64 one shows up as an attachment in both.

Looking at Attachment interface in the docs, it does show a base64 option, but only to decode it as far as I understand in `inject_v1.rs`.

Could this be caused by the fix non-conformance as the docblock mentions?

Some parts where I noticed this:

Please share details on what you’re actually injecting, and the version of KumoMTA you’re using.

Thanks!

We run this commit via Docker: sha-813c793

The payload we send to the HTTP injection endpoint looks like this:

{
  "envelope_sender": "feedback-3734b1f2-e49a-4769-8a02-fb6ecd852146@xxx.xxx.com",
  "recipient": "xxx@xxx.com",
  "content": {
    "from": {
      "email": "info@xxx.com",
      "name": "xxx Test"    },
    "subject": "=?UTF-8?Q?[Test=202\/2]=20CSV=20attachment=20with=20Content-Type:=20text\/csv?=",
    "headers": {
      "Message-ID": "<3734b1f2-e49a-4769-8a02-fb6ecd852146@xxx.net>",
      "Date": "Sat, 14 Mar 2026 11:31:03 +0000",
      "To": "xxx@xxx.com"    },
    "html_body": "<p>CSV - test reproduction.<\/p><p>Please check the attachment headers in the received email.<\/p><img src=\"http:\/\/p10000.localhost\/t\/o\/YTc5OWZiN2YwYTY2MzEzNDY3ZDdlZjAwMzQ0ODIyZjU5YjgzMTQzYmM1M2NhZDI4NDkwYThmMmNhNzMyODVhM3x7Im1lc3NhZ2VfaWQiOiIzNzM0YjFmMi1lNDlhLTQ3NjktOGEwMi1mYjZlY2Q4NTIxNDYiLCJyZWNpcGllbnRfZW1haWwiOiJnMTBzeXAwa3VsQG1haWx0b3dpbi5jb20iLCJleHBpcmVzX2F0IjoxNzc2MDc5ODY2LCJwcm9qZWN0X2lkIjoiOWVlZjc5ZjEtMGEyNy00MmUxLThhMDktY2M0YmZiZWRkM2Y1In0=\" width=\"1\" height=\"1\" style=\"display:none\" alt=\"\">",
    "text_body": "CSV - test reproduction.\r\n\r\nPlease check the attachment headers in the received email.",
    "attachments": [
      {
        "data": "SUQ7VmFsdWUNCjE7VGVzdA0KMjtUZXN0Mg0KMztUZXN0Mw==",
        "base64": true,
        "content_type": "text\/csv",
        "file_name": "test.csv"
      }
    ]
  }
}

It sounds like the message is being rebuilt after generation, which is where the damage is coming from; is that something you’re doing in your policy?

Yep that’s indeed set up. We use the check_fix_conformance. Though I will check if we still need this, just a bit anxious on edge cases still haha

	local failed = msg:check_fix_conformance(
		-- check for and reject messages with these issues:
		"MISSING_COLON_VALUE",
		-- fix messages with these issues:
		"LINE_TOO_LONG|NAME_ENDS_WITH_SPACE|NEEDS_TRANSFER_ENCODING|NON_CANONICAL_LINE_ENDINGS|MISSING_DATE_HEADER|MISSING_MESSAGE_ID_HEADER|MISSING_MIME_VERSION"
	)

I think the rebuild is triggered by long Subject header you provide; you could avoid that by removing LINE_TOO_LONG from the list of fixes.

I do think that the rebuild should try to preserve the attachment disposition. And we probably should also automatically wrap the subject header during the initial build.

Forgot to reply - but if I were to remove that fix, it will break if the initial build isn’t wrapped either right? As in, delivery might be impacted.

Will do some testing and come back!