I'm writing a Ruby script that send SMTP email. The email is divided onto 3 parts: headers, body and attachment. Unfortunatly, I'm struggling to satisfy an Amavis requirement: properly writing the encapsulation boundary.
This header, added by Amavis, noticed the issue:
X-Amavis-Alert: BAD HEADER SECTION, MIME error: error: multipart boundary is missing, or contains CR or LF
I've read the section 7_2 of the RFC 1341, which defines the multipart syntax.
Note that the encapsulation boundary must occur at the beginning of a line, i.e., following a CRLF, and that that initial CRLF is considered to be part of the encapsulation boundary rather than part of the preceding part. The boundary must be followed immediately either by another CRLF and the header fields for the next part, or by two CRLFs, in which case there are no header fields for the next part (and it is therefore assumed to be of Content-Type text/plain).
- encapsulation boundary must follow a CRLF
- encapsulation boundary must be followed immediatly by another CRLF + the header fields of the next part
I believe my script is respecting these two rules, however, some mail clients doesn't recognize the attachment. Here's the raw email (notice that each encapsulation boundaries are preceeded and followed by a CRLF):
From: from@domain.tld\nTo: to@domain.tld\nSubject: =?UTF-8?B?RMOpY2xhcmF0aW9uIHNpbXBsaWZpw6llIGRlIHZpb2xlbmNl?=\nDate: 2020-12-17 15:59:14 +0100\nMIME-Version: 1.0\nContent-Type: multipart/mixed; boundary=_3c7d2a21904930ec7ff47d0cb268c6605a8d06c02dc50e0c5498926371fae06a68d7\r\n--_3c7d2a21904930ec7ff47d0cb268c6605a8d06c02dc50e0c5498926371fae06a68d7\r\nContent-Type: text/html;charset=\"utf-8\"\nContent-Transfer-Encoding:utf8\r\n\nEMAIL BODY CONTENT HERE\r\n--_3c7d2a21904930ec7ff47d0cb268c6605a8d06c02dc50e0c5498926371fae06a68d7\r\nContent-Type: multipart/mixed; name = \"declaration_171220_155914166.pdf\"\nContent-Transfer-Encoding:base64\nContent-Disposition: attachment; filename = declaration_171220_155914166.pdf\n\nJVBERi0xLjMKJf///...(<- Base64 encoded attachment)...\r\n--_3c7d2a21904930ec7ff47d0cb268c6605a8d06c02dc50e0c5498926371fae06a68d7--
What am I doing wrong ? What do you think prevents intolerant clients to display the attachment ?
If it can help anyone, here's my Ruby script
require 'base64'
module Reports
module SMTP
BOUNDARY = '_3c7d2a21904930ec7ff47d0cb268c6605a8d06c02dc50e0c5498926371fae06a68d7'
def self.send_report(file_path)
file_content = File.binread(file_path)
encoded_content = [file_content].pack('m') # Base64
email_content = headers + attachment(file_path, encoded_content) + body
begin
Net::SMTP.start('groupware.sdis21.org', 25, 'HELO FQDN', 'username', 'password', :plain) do |smtp|
smtp.send_message(email_content, 'from@domain.tld', ['to@domain.tld'])
end
rescue => e
puts e.inspect, e.backtrace
end
end
def self.headers
<<~EOF
From: from@domain.tld
To: to@domain.tld
Subject: =?UTF-8?B?#{Base64.strict_encode64('Déclaration simplifiée de violence')}?=
Date: #{Time.now.to_s}
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=#{BOUNDARY}\r
--#{BOUNDARY}\r
EOF
end
def self.body
<<~EOF
Content-Type: text/html;charset="utf-8"
Content-Transfer-Encoding:utf8
EMAIL BODY CONTENT HERE\r
--#{BOUNDARY}\r
EOF
end
def self.attachment(file_path, encoded_content)
file_path = file_path.split('/').last
<<~EOF
Content-Type: multipart/mixed; name = "#{file_path}"
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename = #{file_path}
#{encoded_content}\r
--#{BOUNDARY}--\r
EOF
end
end
end