E-Mails mit Lua via SSMTP versenden

SSMTP

Das Programm SSMTP ist ein sehr einfacher Mail-Transport Agent, der bei Bedarf die zu versendene E-Mail an einen entsprechenden Server weiterleitet. Es leitet die — auf vielen Unix-Systemen verwendeten — Befehle zum E-Mail-Versand sendmail sowie mail auf sich um. Das Programm läuft jedoch nicht als Server und bricht bei Fehlern schlicht mit einer Fehlermeldung den Versand ab. SSMTP ist auf vielen Linux-Distributionen über Pakete verfügbar und somit leicht installierbar. Seine Konfiguration hängt von dem verwendeten Provider und dessen E-Mail-Einstellungen ab. Für einen einfachen Provider kann die folgende Konfiguration der Datei /etc/ssmtp.conf (oder — in Abhängigkeit der verwendeten Distribution — aber auch /etc/ssmtp/ssmtp.conf) möglicherweise als Beispiel dienen.

root=postmaster
mailhub=smtp.provider.net:465
hostname=mein.server.com
FromLineOverride=YES
UseTLS=YES
AuthUser=username
AuthPass=passwort

Auf der Shell kann die Konfiguration durch den Befehl
printf "SUBJECT: Betreff-Zeile\nFROM: <von@mir.de>\nTO: zu@dir.de\n\nHallo\nBody" | ssmtp zu@dir.de
getestet werden. Man beachte, dass der Empfänger zu@dir.de hier doppelt angegeben wird: Die erste Angabe dient dabei lediglich der Anzeige in der E-Mail, die dem Empfänger (zweite Angabe) angezeigt wird.

Lua

Die Skript-Sprache Lua hat sich als sehr effizient und performant erwiesen, so dass es gerne im VoIP- sowie im Game-Umfeld eingesetzt wird. Aber auch im Web-Bereich findet es mit bspw. HASERL oder in LuCi Verwendung.

Das einfache Versenden einer reinen Text-E-Mail ist über den Umweg eines Shell-Befehles möglich.

local sendto = "zu@dir.de"
local sendfrom = "von@mir.de"
local subject = "Betreff-Zeile"
local body ="Text der E-Mail\nmit dem Zeichen \\\"\\\\\\n\\\" werden die Zeilen voneinander getrennt."
local header ="Subject: "..subject .."\nFrom: "..sendfrom.."\nTo: "..sendto .."\n"

os.execute("echo -e \""..header .."\n"..body.."\" |ssmtp ".. sendto)

Sollen jedoch auch Dateien der E-Mail beigefügt werden, so sollte der Inhalt der E-Mail richtig als mehrteilig formatiert werden. Da Dateien im Allgemeinen oft nicht nur ASCII-Zeichen beinhalten, sind diese vor dem Versand mit Base64 zu encodieren sowie auf die MIME Zeilenlänge von 74 Zeichen zu begrenzen.

-- Lua 5.1+ base64 v3.0 (c) 2009 by Alex Kloss 
-- licensed under the terms of the LGPL2
local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
function base64(data)
    return ((data:gsub('.', function(x)
        local r,b='',x:byte()
        for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
        return r;
    end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
        if (#x < 6) then return '' end
        local c=0
        for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
        return b:sub(c+1,c+1)
    end)..({ '', '==', '=' })[#data%3+1]):gsub("(%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g)","%1\r\n")
end

Damit kann der Versand einer TIFF-Datei und einer PDF-Datei mit Lua wie folgt geschehen.

local sendto = "zu@dir.de"
local sendfrom = "von@mir.de"
local subject = "Betreff-Zeile"
local body ="Text der E-Mail\nmit dem Zeichen \\\"\\\\\\n\\\" werden die Zeilen voneinander getrennt."
local files = {"/pfad/zur/Datei.tif","/pfad/zur/Datei.pdf"}

local header ="Subject: "..subject .."\nFrom: "..sendfrom.."\nTo: "..sendto .."\n"
local contentType = {["tif"]="image/tiff",["pdf"]="application/pdf",["wav"]="audio/wav"}
local boundary = "--------voiscout-boundary-------"
local mailbody = "MIME-Version: 1.0\n"..
      "Content-Type: multipart/mixed; boundary="..boundary.."\n\n"..
      "This is a multi-part message in MIME format.\n--"..boundary.."\n" ..
      "Content-Type: text/plain; charset=utf-8\n"..
      "Content-Transfer-Encoding: quoted-printable\n\n"..body.."\n--"..boundary

for i,file in ipairs(files) do
  local filename = file:match("[^/]*$")
  local f = io.open(file, "r")
  if f then
     local filecontent = f:read("*all")
     f:close()
     if filecontent then
        mailbody = mailbody .."\n"..
            "Content-Type: "..contentType[filename:match("[^%.]*$")].."; name=\\\""..filename.."\\\"\n"..
            "Content-Transfer-Encoding: base64\n"..
            "Content-Disposition: attachment; filename=\\\""..filename.."\\\"\n\n" 
            ..base64(filecontent).."\n--"..boundary
      end 
   end 
end                                                           
os.execute("echo -e \""..header..mailbody.."--\" |ssmtp "..sendto)

Sollte durch die Größe der Datei das Limit für Lua zum Absetzen eines Befehls auf die Shell überschritten werden, sollten entsprechende Teile der E-Mail zunächst in eine temporäre Datei geschrieben und diese der E-Mail angefügt werden.

os.execute("(echo -e \""..header..mailbody.."\" && cat "..tmpfile..") |ssmtp "..sendto)

Bei dem Empfang von UTF-8 codierten E-Mails kommt es regelmäßig zu fehlerhaften Darstellungen. Diese können dadurch vermieden werden, dass der Text Quoted-Printable-kodiert wird. Dieses kann beispielsweise durch die folgende Funktion erfolgen.

function qp(txt)
  return 
   txt:gsub("([\000-\031\127-\255=])",function(c) return string.format("=%02X", c:byte()) end) 
   :gsub(string.rep(".",72), function(x)
      return x:find("=",-2) and (x:sub(1,-3) .. x:sub(-2):gsub("=","=\n=")) or x.."=\n"
    end)
end

Soll die E-Mail einen HTML-Teil enthalten, so ist der text/plain Teil typischerweise in einem

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

Block mit einer eigenen Boundary eingebettet. Der letzte dieser alternativen Teile wird jeweils versucht anzuzeigen, so dass der HTML-Teil normalerweise der reinen Text-Alternative folgt und somit der letzte ist. Beim Verfassen des HTML-Teils ist jedoch besondere Vorsicht geboten, da die unterschiedlichen E-Mail-Programme jeweils unterschiedliche Teile des HTML-Standards nur darstellen.