Es wird ja öfters hier gefragt, wie erstelle ich eine Mail mit einem Bild oder eigenem HTML, welche auch bei anderen Mailclients vernünftig dargestellt werden. Ich habe mir deshalb gedacht, ich schreib mal einen kleinen Artikel. Verbesserungsvorschläge, Lob und Kritik nehme ich gerne an.
Wie erstelle ich eine Mail mit einem Bild oder eigenem HTML, welche auch bei anderen Mailclients vernünftig dargestellt werden?Die einfache Lösung ist, ein entsprechendes RichtextItem zu erzeugen und dem Mailrouter die Konvertierung nach HTML zu überlassen.
Das Ergebnis ist leider oft nicht akzeptabel.
Die Alternative ist, man erzeugt das HTML selber und packt es in eine Mail. Doch Wie?
LotusScript bietet für diesen Zweck eine ganze Reihe von NotesMime-Klassen an, mit denen sich dieses Vorhaben umsetzen lässt.
WARNUNGDer erste Kontakt mit diesen Klassen ist meist frustrierend, weil als Ergebnis nicht das herauskommt was man will. Die Klassen sind zwar relativ gut dokumentiert, aber man muss die Hilfe schon sehr genau lesen. In dem ein oder anderen Nebensatz ist oft erwähnt, was man nicht machen soll oder machen muss...
Ich habe deshalb hier mal die wichtigsten Fallstricke zusammengefasst:
- session.convertMime steht standardmäßig auf TRUE.
Notes konvertiert dann ein MIME-Item beim Zugriff über das Item-Interface zurück auf ein Richtext-Item. Dadurch geht oft die ein oder andere Formatierung verloren.
Viel schlimmer ist aber, dass der Richtext beim Senden der Mail wieder vom Mailrouter nach HTML/MIME konvertiert wird und somit wenig Ähnlichkeit zum Original-MIME besteht.
(Man kann aber dieses Verhalten auch nutzen, wenn man z.B. eine komplizierte Tabellenstruktur o.ä. in Richtext benötigt. Diese erzeugt man sich mit HTML und überlässt dann Notes die Rückkonvertierung)
- Wenn man session.convertMime verstellt, sollte man anschließend den ursprünglichen Wert (welcher nicht zwangsläufig TRUE ist) wiederherstellen.
Zitat Hilfe: This setting is initially True but persists across Notes client events. In general, if you change this setting, you should restore it before exiting.
- Die Mime-Verarbeitung darf nicht durch andere Zugriffe auf das Dokument gestört werden.
Nach einem get/createMimeEntity dürfen im Dokument keine Felder gelesen/gesetzt werden oder weitere Mime-Items parallel bearbeitet werden.
Man muss mit CloseMimeEntities die Bearbeitung abschließen. Ansonsten treten Abstürze auf.
Zitat Hilfe: Avoid processing items as both NotesItem and NotesMIMEEntity objects concurrently. Once you start using NotesMIMEEntity properties and methods, do not use other methods that access items in the same document until you terminate MIME processing with ComputeWithForm, Encrypt, Save, Send, or Sign, or CloseMIMEEntities in NotesDocument.
- Header wie "Subject", "To", "Recieved" direkt über die entsprechenden Items setzen
Zitat Hilfe: In a mail message, certain items correspond to MIME headers, for example, Subject ("Subject" header), SendTo ("To" header), and Received ("Received" header).
- Encoding-Probleme sind an der Tagesordnung
Ein "charset=UTF-8" an der richtigen Stelle wirkt oft Wunder. Den Code sollte man in jedem Fall mit Umlauten und ggf. auch weiteren Sonderzeichen testen.
Vor dem Auslesen eines Mime-Items (auch Child-Items) sollte man das Item mit mime.DecodeContent dekodieren. Dabei werden Koduerungen wie "quoted-printable" etc. entfernt. (Ansonsten erhält man z.B. =FC für ein ü)
- Mime-Items lassen sich nicht richtig mit copyItemToDocument kopieren.
Siehe hier: http://atnotes.de/index.php/topic,50380.0.html
- Mime-Items können bei falscher Handhabung korrupte Dokumente erzeugen
Dies tritt z.B. auf, wenn man nur ein "multipart/related" Root-Item hat, aber keine weiteren.
Es kann sogar passieren dass man die Dokumente nicht mehr löschen kann: http://atnotes.de/index.php/topic,51837.0.html (wenngleich die Fehlerursache hier eine andere war)
- OffTopic:Ein Mime-Item lässt sich zur Base64 (de)kodierung zweckentfremden
Set doc=New NotesDocument(db)
Set miment=doc.CreateMIMEEntity
Call miment.SetContentFromBytes(myStream, "", ENC_IDENTITY_BINARY)
Call miment.EncodeContent(ENC_BASE64)
EncodeFile=miment.ContentAsText()
(Alternativ kann auch die undokumentierte Funktion notesStream.readEncoded/writeDecoded verwendet werden)
So, genug gewarnt. Wie erstelle ich jetzt so ein MIME-Item?Zuvor noch ein paar Grundlagen:
MIME = Multipurpose Internet Mail Extension. Erweitert das Email-Format, so dass nicht nur normaler ASCII-Text genutzt werden kann.
Ein Mime-Item ist eine Art Baumstruktur,besteht aus einem Wurzelknoten, welche Kindknoten besitzen können. Ein Kindkoten kann weitere Kindknoten besitzen.
Jeder Knoten hat einen ContentType und ein oder mehrere Header. (Mehr dazu findet man
http://de.wikipedia.org/wiki/MIME und in den RFCs)
Im Beispiel beschränke ich mich auf folgende Baumstruktur
+ multipart/related
+ multipart/alternative (es wird der letzte Part, sowit unterstützt, angezeigt)
+ text/plain
+ text/html
+ image/gif (optional)
+ ... weiter Anhänge etc.
Hier habe ich einen Beispiel, wie man eine HTML-Email erzeugt, ein Bild einfügt und versendet. Das Beispiel sollte relativ selbsterklärend sein.
Option Public
Option Declare
%Include "LSCONST.LSS"
Private Const LARRY = |R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAw
AAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFz
ByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSp
a/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJl
ZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uis
F81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PH
hhx4dbgYKAAA7|
Private Const HTML = |Dieses Bild zeigt <img src="%IMAGE%"> Larry von der <a href="http://www.ietf.org/rfc/rfc2397.txt">Network Working Group</a><br>|
'/**
' * Diese Klasse dient zum Erstellen von Multipart-Related Mime-Items
' * @author: Roland Praml/01/int/FOCONIS
' */
Public Class MultipartRelatedMime
Private doc As NotesDocument
Private itemName As String
Private session As NotesSession
Private oldCvtMime As Integer
Private rootMime As NotesMIMEEntity
Private contentMime As NotesMIMEEntity
'/**
' * Erzeuge ein neues Objekt dieser Klasse. Im Anschluss können mittels
' * "attach" beliebig viele Attachments angelegt werden. Will man diese
' * im HTML anzeigen, so muss man sich den Rückgabewert von Attach merken
' * und als srv-URL im IMG-Tag verwenden (siehe Beispiel)
' * @param doc das Dokument, auf dem das MIME-Processing statt findet
' * @param itemName das MIME-Item (Normalerweise Body)
' * @return ein Objekt vom Typ MultipartRelatedMime
' */
Public Sub new(doc As NotesDocument, itemName As String)
Set me.doc = doc
Me.itemName = itemName
If doc.Hasitem(Itemname) Then ' Sicherheitsprüfung
Error 1000, "The Item " + itemName +" already exists."
End if
Set session = New NotesSession
oldCvtMime = session.convertMime ' alte Einstellung merken
session.convertMime = False ' Konvertierung abschalten
' -- Anlegegen des Root-Mimes
Dim stream As NotesStream
Set rootMime = doc.CreateMIMEEntity(itemName)
Set stream = session.CreateStream()
Call stream.WriteText("This is a multipart message in MIME format.")
Call rootMime.SetContentFromText(stream, "multipart/related", ENC_NONE )
' !! Es wird auch gleich ein Dummy-content angelegt, denn wenn man
' !! das Dokument jetzt speichert, dann wird es korrupt
Set contentMime = rootMime.Createchildentity()
Call stream.truncate()
Call stream.WriteText("empty")
Call contentMime.SetContentFromText(stream, "text/plain", ENC_NONE )
Call stream.Close()
End Sub
'/**
' * Destruktor, erledigt noch ein paar Aufräumarbeiten.
' * !! Achtung, danach ist die MIME-Konvertierung wieder aktiviert. !!
' */
Public Sub Delete
Call closeMime() ' sicherheitshalber nochmals close aufrufen
session.Convertmime = oldCvtMime '
End Sub
'/**
' * Diese Methode beendet die MIME-Verarbeitung, erst danach darf wieder
' * auf das Dokument zugegriffen werden.
' */
Public Function closeMime()
If Not doc Is Nothing Then
Set rootMime = Nothing
Set contentMime = Nothing
Call doc.Closemimeentities(true, itemName)
Set doc = nothing
End If
End Function
'/**
' * "attach" hängt eine Datei an das MIME an. Die FileData werden idR.
' * binär oder base64-codiert übergeben. Gibt man einen Filenamen an,
' * so erscheint die Datei als Attachment, andernfalls erscheint bei
' * Bildern, welche im HTML referenziert werden, kein zusätzliches
' * Attachmentsymbol/"Büroklammer"
' * @param fileData Die Filedaten
' * @param encoding Das Enoding, z.B. ENC_BASE64
' * @param filename Der Dateiname, kann auch leer sein, s.o.
' * @param contentType z.B.: gif/jpeg
' * @return content-identifier
' */
Public Function attach(fileData As NotesStream, encoding As Integer, _
filename As String, ByVal contentType As String) As String
' -- es wird ein neues Kind angelegt
Dim mimePart As NotesMIMEEntity
Set mimePart = rootMime.CreateChildEntity()
If contentType = "" then
contentType = "application/octet-stream" ' Default
End if
If filename <> "" Then
' Wichtig: Dateinamen sollten nicht doppelt vorkommen.
' 18.04.14/RPr: Encoding ergänzt. Dateinamen dürfen
' nun auch Umlaute enthalten
Call mimePart.CreateHeader("Content-Disposition"). _
SetheaderValAndParams({attachment; filename="} + _
encodeFileNameRFC2047(filename) + {"})
End If
' -- eine zufällige Content-ID erzeugen
Dim cid As String
Dim randomGenerator As New NotesDocument(doc.Parentdatabase)
cid = randomGenerator.Universalid+"@mydomain"
Call mimePart.CreateHeader("Content-ID")._
SetheaderValAndParams("<" + cid + ">")
' -- und die Daten schreiben
Call mimePart.SetContentFromText(fileData, contentType, encoding)
attach = "cid:" + cid
End Function
'/**
' * Schreibt das HTML, sowie einen alternativen Plaintext. Dieser sollte
' * immer geschrieben werden und ggf die Information enthalten, dass ein
' * HTML-Fähiger Mailclient vorhanden sein muss.
' * @param html der HTML-Quelltext
' * @param plain der Plain-Text
' */
Public Function setHtml(html As String, plain As String)
' - das ursprüngliche ContentMime wird ersetzt
Dim nextSibling As NotesMIMEEntity
Set nextSibling = contentMime.Getnextsibling()
Call contentMime.Remove() ' in dem man es löscht
Set contentMime = rootMime.Createchildentity(nextSibling) ' und wieder anlegt
Dim stream As NotesStream
Set stream = session.createStream()
Call stream.WriteText("This is a message with multiple parts in MIME format.")
Call contentMime.SetContentFromText(stream, "multipart/alternative", ENC_NONE )
' -- darunter kommt der Plaintext
Dim textPlain As NotesMIMEEntity
Set textPlain = contentMime.CreateChildEntity()
Call stream.truncate()
Call stream.WriteText(plain)
Call textPlain.SetContentFromText(stream, "text/plain", ENC_NONE )
' -- und der HTML-Text
Dim textHtml As NotesMIMEEntity
Set textHtml = contentMime.CreateChildEntity()
Call stream.truncate()
Call stream.WriteText(html)
Call textHtml.setContentFromText(stream, "text/html; charset=UTF-8", ENC_NONE)
Call stream.Close
End Function
End Class
'/**
' * Diese Prozedur erstellt ein neues Dokument, erzeugt mit obiger Klasse ein MIME-Item und versendet das Dokument dann
' */
Sub Initialize
Dim session As New NotesSession
Dim stream As NotesStream
Dim doc As New NotesDocument(session.Currentdatabase)
Dim cid As String
doc.Subject = "Dies ist ein Test"
Dim mime As New MultipartRelatedMime(doc, "Body") ' Klasse anlegen
Set stream = session.createStream
Call stream.Writetext(LARRY)
cid = mime.attach(stream, ENC_BASE64, "", "image/gif") ' Bild anhängen und CID merken
Call stream.close()
Call mime.setHtml(Replace(HTML, "%IMAGE%", cid), "bitte benutzen Sie einen HTML-fähigen Mailclient") ' CID im HTML ersetzen
Call mime.closeMime()
Call doc.send(False,session.Effectiveusername) ' Mail an den aktuellen User senden (zum Testen senden wir uns das Mail selber)
End Sub
Tipps
- Durch kleine Modifikationen lassen sich auch .ics - Einladungen an Outlook versenden:
http://atnotes.de/index.php/topic,53222.0.html- Durch Setzen von session.convertMime = true wird beim nächsten Zugriff auf das Item (über doc.getFirstItem) das Mime-Item von Notes wieder in einen Richtext konvertiert. Dieses Item kann dann an ein anderes Richtext-Item angefügt werden.
Damit kann man z.B. speziell formatierte Texte/Links/Bilder/... an ein bestehendes RICHTEXT-Item anhängen.
Gruß
Roland
/Edit 18.4.2014
Damit auch Umlaute in Dateinamen unterstüzt werden, muss man diese encodieren. Dazu verwende ich folgende Methode:
Public Function encodeFileNameRFC2047(fileName As String) As String
Dim c As Integer
Dim i As Integer
Dim ret$
Dim mode$
For i = 1 To Len(fileName)
c = Uni(Mid(filename, i, 1))
If (32 <= c And c <= 60) Or (62 <= c And c <= 126) Then
' printable
ret = ret + Chr(c)
ElseIf c < 256 Then
mode = "Q"
ret = ret + "=" + Right("00" + Hex(c),2)
Else
mode = "B"
Exit For
End If
Next
If mode = "" Then
encodeFileNameRFC2047 = fileName
ElseIf mode = "Q" Then
encodeFileNameRFC2047 = "=?iso-8859-1?Q?" + ret+ "?="
ElseIf mode = "B" Then
Dim stream As NotesStream
Set stream = FocUtil.getSession().createStream()
Call stream.writetext(filename)
Dim mime As NotesMIMEEntity
Dim tmpDoc As New NotesDocument(FocUtil.getSession().currentDatabase)
Set mime = tmpDoc.Createmimeentity("Roland")
Call mime.Setcontentfromtext(stream, "text/html; charset=UTF-8", ENC_NONE)
stream.position = 0
Call mime.Encodecontent(ENC_BASE64)
Set stream = FocUtil.getSession().createStream()
Call mime.Getcontentastext(Stream, False)
stream.position = 0
stream.position = 0
Dim s(1) As String, r(1) As String
s(0) = Chr(10)
s(1) = Chr(13)
ret = Replace(stream.readText, s, r)
encodeFileNameRFC2047 = "=?UTF-8?B?" + ret+ "?="
Call tmpDoc.Closemimeentities(False)
End If
End Function