Autor Thema: [LotusScript] Der Umgang mit Mime-Items - oder wie erstelle ich ein HTML Mail  (Gelesen 55952 mal)

Offline pram

  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 1.170
  • Geschlecht: Männlich
    • Foconis Object Framework
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.

WARNUNG
Der 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
Code
+ 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.

Code
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:
Code
	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



« Letzte Änderung: 18.04.14 - 23:53:07 von pram »
Roland Praml

IBM Certified Application Developer - Lotus Notes and Domino 8
Ich verwende das Foconis Object Framework

Offline ata

  • Freund des Hauses!
  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 5.092
  • Geschlecht: Männlich
  • drenaiondrufflos
    • Anton Tauscher Privat
Re: [LotusScript] Der Umgang mit Mime-Items
« Antwort #1 am: 30.06.11 - 07:56:07 »
Hallo Roland,

danke für die schöne Einführung, die Hinweise und den Beispielcode - das werde ich mir mal in Ruhe zu Gemüte führen und ein wenig damit spielen.

Toni ;D ;D ;D
Grüßle Toni :)

Offline Bruce Willis

  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 1.002
  • Geschlecht: Männlich
  • Wer nicht wagt...
...Verbesserungsvorschläge, Lob und Kritik nehme ich gerne an...

Hallo Roland,

super Sache, vielen Dank!

Weißt Du, wie man die hier angehängte Fehlermeldung beseitigen kann?

Gruß
Leo
nobody is perfect but i'm pretty close 

Offline ata

  • Freund des Hauses!
  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 5.092
  • Geschlecht: Männlich
  • drenaiondrufflos
    • Anton Tauscher Privat
... kann eigentlich nur an deiner Variablen HTML liegen...

Bitte nicht so alte Threads reanimieren - danke

Toni ;-)))
Grüßle Toni :)

Offline Bruce Willis

  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 1.002
  • Geschlecht: Männlich
  • Wer nicht wagt...
Hallo Toni,

Danke für dein Input!

... kann eigentlich nur an deiner Variablen HTML liegen...
Kannst du bitte deine (funktionierende) vorschlagen?

Bitte nicht so alte Threads reanimieren
Ich werde mich bemühen...  ;)

Gruß
Leo
nobody is perfect but i'm pretty close 

Offline koehlerbv

  • Moderator
  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 20.460
  • Geschlecht: Männlich
Schalte erstens Option Declare ein, Leo.

Weiters: Roland hat die Constant HTML definiert. Du auch?

Bernhard

Offline Bruce Willis

  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 1.002
  • Geschlecht: Männlich
  • Wer nicht wagt...
Hallo Bernhard,

Danke für die Teilnahme!  :)

Schalte erstens Option Declare ein, Leo.
Immer an!
Bei Dir nicht?  :P

Roland hat die Constant HTML definiert. Du auch?
Klar! Ich hab 1zu1 seinen Script zum Testen übernommen, um nichts kaputt zu machen.
 :-:

Kannst Du bitte eine andere evtl. funktionierende vorschlagen?

Gruß
Leo
nobody is perfect but i'm pretty close 

Offline koehlerbv

  • Moderator
  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 20.460
  • Geschlecht: Männlich
Na, wenn Du das schon so fein machst (vielleicht besser als ich?), dann zeige doch mal im Debugger den Inhalt von "HTML" zu Zeitpunkt, wenn es "bumm" macht" ...

Bernhard

Offline Bruce Willis

  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 1.002
  • Geschlecht: Männlich
  • Wer nicht wagt...
Ooooh, sorry, Du hast recht!
option declare war doch nicht bei dem Button sondern nur in der Maske...
 ??? ::)

Jetzt sehe ich in rot:

Call stream.Writetext(LARRY)

LARRY müsste noch irgenwie deklariert werden.
Weißt Du, wie?
nobody is perfect but i'm pretty close 

Offline koehlerbv

  • Moderator
  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 20.460
  • Geschlecht: Männlich
Benutze doch einfach die Suche in diesem Threadtext (Ctrl-G im Firefox, Ctrl-F im IE) und gib LARRY ein. Und HTML dann ... Herrjeh, Leo - Du kannst das doch. Ist es zu spät heute?

Bernhard

Offline Bruce Willis

  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 1.002
  • Geschlecht: Männlich
  • Wer nicht wagt...
Danke für den Hinweis!
Jetzt bekomme ich aber eine andere Meldung...  :(
Ich gehe lieber schlafen...

Gruß
Leo
« Letzte Änderung: 12.03.12 - 08:57:26 von Bruce Willis »
nobody is perfect but i'm pretty close 

Offline koehlerbv

  • Moderator
  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 20.460
  • Geschlecht: Männlich
Und jetzt ist die Zeit, die DesignerHelp auszupacken oder ein gutes Buch und die Grundlagen dessen zu erlernen, was man verwenden möchte. Du kennst ja die Prinzipien von AtNotes, Leo: Hilfe immer gerne, aber keine Vorkauen und vor allem keine Übernahme von Arbeiten.

Bernhard

Offline Bruce Willis

  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 1.002
  • Geschlecht: Männlich
  • Wer nicht wagt...
Ich habe ausgeschlafen, und der Script funktioniert einwandfrei.   :)
Danke an alle Beteiligten!

Gruß
Leo
nobody is perfect but i'm pretty close 

 

Impressum Atnotes.de  -  Powered by Syslords Solutions  -  Datenschutz