Bertrand Martel
Bertrand Martel

Reputation: 45432

curl : send html email with embedded image and attachment

My goal is to send an email using curl with an html body with embedded image such as :

email template image

I'm sending the email like this :

curl "smtp://smtp.gmail.com:587" -v \
     --mail-from "[email protected]" \
     --mail-rcpt "[email protected]" \
     --ssl -u [email protected]:secretpassword \
     -T "message.txt" -k --anyauth

My message.txt looks like :

From: Some Name <[email protected]>
To: Some Name <[email protected]>
Subject: example of mail
Reply-To: Some Name <[email protected]>
Cc: 
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="MULTIPART-MIXED-BOUNDARY"

--MULTIPART-MIXED-BOUNDARY
Content-Type: multipart/alternative; boundary="MULTIPART-ALTERNATIVE-BOUNDARY"

--MULTIPART-ALTERNATIVE-BOUNDARY
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: base64
Content-Disposition: inline

PGh0bWw+Cjxib2R5PgogICAgPGRpdj4KICAgICAgICA8cD5IZWxsbywgPC9wPgogICAgICAgIDxw
PlBsZWFzZSBzZWUgdGhlIGxvZyBmaWxlIGF0dGFjaGVkPC9wPgogICAgICAgIDxwPkFkbWluIFRl
YW08L3A+CiAgICAgICAgPGltZyBzcmM9ImFkbWluLnBuZyIgd2lkdGg9IjE1MCIgaGVpZ2h0PSI1
MCI+CiAgICA8L2Rpdj4KPC9ib2R5Pgo8L2h0bWw+Cg==
--MULTIPART-ALTERNATIVE-BOUNDARY--
--MULTIPART-MIXED-BOUNDARY

The html decoded is :

<html>
<body>
    <div>
        <p>Hello, </p>
        <p>Please see the log file attached</p>
        <p>Admin Team</p>
        <img src="admin.png" width="150" height="50">
    </div>
</body>
</html>

How can I embed admin.png in this html and attach another file log.txt to this email using curl and bash ?

Upvotes: 11

Views: 26785

Answers (4)

Bryan Palmer
Bryan Palmer

Reputation: 1

The answer by @bertrand-martel was mostly right on my system running Ubuntu 22.04 and submitting to a postfix server in my network. The html email message came through just fine, and I had empty attachments named correctly. I had to edit the add_file function he defined and add in a blank line before the $3 at the very end. The email would not work without a blank line between the Content-Disposition: and the base64 encoding. I also had to insert a blank line after the --MULTIPART-ALTERNATIVE-BOUNDARY-- . Once I made those changes, the html message, the embedded image, and the attached file all came across correctly.

Upvotes: 0

mdk
mdk

Reputation: 6543

Here's a shell script you could use. I heavily derived from https://blog.ambor.com/2021/08/using-curl-to-send-e-mail-with.html

Passwords could be stored in a .netrc file as discussed on: https://everything.curl.dev/usingcurl/netrc but I haven't tried that.

Just make sure the image you need is embedded in the HTML using base64 encoding.

#!/bin/bash

declare -a VOPTS;
declare -a HOPTS;

sesAccess="[email protected]" ;
sesSecret="sender.account.passwordXXXXXX";
sesFromName="Sender Full Name";
sesFromAddress='<[email protected]>';
sesToName="Recipient Full Name";
sesToAddress="<[email protected]>"
sesSubject="Email Subject Line";
sesSMTP='mail.server.fqdn';
sesPort='465';
sesMessage=$'Test of line 1\nTest of line 2'
sesFile="$1"; # attachment is first argument
sesHTMLbody="/path/to/html/file.html"; # content of this file will be used to create HTML body

sesMIMEType=`file --mime-type "$sesFile" | sed 's/.*: //'`;
# sesMIMEType=`file -b --mime-type "$sesFile"`;

VOPTS=();
HOPTS=();

#Curl Options
VOPTS+=("-v");
VOPTS+=("--url"); VOPTS+=("smtps://$sesSMTP:$sesPort"); 
VOPTS+=("--ssl-reqd")
VOPTS+=("--user"); VOPTS+=("${sesAccess}:${sesSecret}"); 
VOPTS+=("--mail-from"); VOPTS+=("${sesFromAddress}");
VOPTS+=("--mail-rcpt"); VOPTS+=("${sesToAddress}");

#Header Options
HOPTS+=("-H"); HOPTS+=("Subject: ${sesSubject}");
HOPTS+=("-H"); HOPTS+=("From: ${sesFromName} ${sesFromAddress}"); 
HOPTS+=("-H"); HOPTS+=("To: ${sesToName} ${sesToAddress}"); 

curl "${VOPTS[@]}" -F '=(;type=multipart/mixed' -F "=<$sesHTMLbody;type=text/html;encoder=base64" -F "file=@$sesFile;type=$sesMIMEType;encoder=base64" -F '=)' "${HOPTS[@]}"

exit

Upvotes: 3

Daniel J.
Daniel J.

Reputation: 366

Plus one, your code example is a bit misleading tough, in fact it doesn't work. As you say, you just have to enclose each part in a boundary, in your case the delimiter is:

"--MULTIPART-MIXED-BOUNDARY".

Nonetheless it could be any other user defined string. You are nesting multiple boundary types, which isn't necessary. HTML content doesn't need to be base64 encoded, it can go encoded if you will though.

You need a blank line after and before each delimiter.

I recommend taking a look at the W3 papers and examples: https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html

Upvotes: 0

Bertrand Martel
Bertrand Martel

Reputation: 45432

The solution I came up with is to base64 encode all the attachment (image and text file) and to include them into the uploaded file directly in the multipart/mixed body such as :

--MULTIPART-MIXED-BOUNDARY
Content-Type: image/png
Content-Transfer-Encoding: base64
Content-Disposition: inline
Content-Id: <admin.png>
iVBORw0KGgoAAAANSUhEUgAAAIAAAACgCAIAAABL8POqAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH4AQNDwEVouBdqAAAG2xJREFUeNrtfX9oHFe25jdDBU5BG25BG7pABhXEkDJjSIsYIs1m
WbfJA8ubhcjjgdiTQNJOYCInj0RKYGIl8CbyPF4iZSCxEkgsB5LIgWQlL2Pcfow3bdgw0mMzox6e
....


--MULTIPART-MIXED-BOUNDARY
Content-Type: text/plain
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename=log.txt
c29tZSBsb2cgaW4gYSB0eHQgZmlsZSB0byBhdHRhY2ggdG8gdGhlIG1haWwK


--MULTIPART-MIXED-BOUNDARY--

The Content-Id header is used to identify the resource that can be referenced in the html with : cid: like :

<img src="cid:admin.png" width="150" height="50">

Here is a complete bash example to send an html email with and admin.png embedded image and a log.txt attached to it :

#!/bin/bash

rtmp_url="smtp://smtp.gmail.com:587"
rtmp_from="[email protected]"
rtmp_to="[email protected]"
rtmp_credentials="[email protected]:secretpassword"

file_upload="data.txt"

# html message to send
echo "<html>
<body>
    <div>
        <p>Hello, </p>
        <p>Please see the log file attached</p>
        <p>Admin Team</p>
        <img src=\"cid:admin.png\" width=\"150\" height=\"50\">
    </div>
</body>
</html>" > message.html

# log.txt file to attached to the mail
echo "some log in a txt file to attach to the mail" > log.txt

mail_from="Some Name <$rtmp_from>"
mail_to="Some Name <$rtmp_to>"
mail_subject="example of mail"
mail_reply_to="Some Name <$rtmp_from>"
mail_cc=""

# add an image to data.txt : 
# $1 : type (ex : image/png)
# $2 : image content id filename (match the cid:filename.png in html document)
# $3 : image content base64 encoded
# $4 : filename for the attached file if content id filename empty
function add_file {
    echo "--MULTIPART-MIXED-BOUNDARY
Content-Type: $1
Content-Transfer-Encoding: base64" >> "$file_upload"

    if [ ! -z "$2" ]; then
        echo "Content-Disposition: inline
Content-Id: <$2>" >> "$file_upload"
    else
        echo "Content-Disposition: attachment; filename=$4" >> "$file_upload"
    fi
    echo "$3

" >> "$file_upload"
}

message_base64=$(cat message.html | base64)

echo "From: $mail_from
To: $mail_to
Subject: $mail_subject
Reply-To: $mail_reply_to
Cc: $mail_cc
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=\"MULTIPART-MIXED-BOUNDARY\"

--MULTIPART-MIXED-BOUNDARY
Content-Type: multipart/alternative; boundary=\"MULTIPART-ALTERNATIVE-BOUNDARY\"

--MULTIPART-ALTERNATIVE-BOUNDARY
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: base64
Content-Disposition: inline

$message_base64
--MULTIPART-ALTERNATIVE-BOUNDARY--" > "$file_upload"

# add an image with corresponding content-id (here admin.png)
image_base64=$(curl -s "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_116x41dp.png" | base64)
add_file "image/png" "admin.png" "$image_base64"

# add the log file
log_file=$(cat log.txt | base64)
add_file "text/plain" "" "$log_file" "log.txt"

# add another image 
#image_base64=$(curl -s "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_116x41dp.png" | base64)
#add_file "image/png" "something.png" "$image_base64"

# end of uploaded file
echo "--MULTIPART-MIXED-BOUNDARY--" >> "$file_upload"

# send email
echo "sending ...."
curl -s "$rtmp_url" \
     --mail-from "$rtmp_from" \
     --mail-rcpt "$rtmp_to" \
     --ssl -u "$rtmp_credentials" \
     -T "$file_upload" -k --anyauth
res=$?
if test "$res" != "0"; then
   echo "sending failed with: $res"
else
    echo "OK"
fi

Upvotes: 18

Related Questions