Embedded attachments in email - url in email opens attachment - how is it done?

I am a Lotus Notes user and was passed an email which contained links (ie urls) in the body of text which, when clicked, prompted Notes to open a specific document.

The attached files can be found in the $FILE fields in the doc properties, but they are not visible in the email. The actual URLs are hotspots which contain URL values similar to:


I know this came from an Exchange server, but I can't contact the sender but would like to emulate this behaviour in some apps that I develop.

Does anyone know how this is done?

Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

The 'cid' tag is a MIME tag that stands for 'Content ID'.  As you have already learned, it points to a resource that is stored in a hidden $FILE item.  In the example above, you should have a $FILE item where the 'File Name' property is 'df5277f9-70e8-495c-9dd2-a0aee336f9d8'.

>> "would like to emulate this behavior in some apps that I develop."

To emulate this, you need to understand how MIME messages are composed and how file attachments are represented in MIME.  In LotusScript, you will need to become familiar with the following classes: NotesMimeEntity, NotesMimeHeader, and NotesStream.

I've never created MIME messages with download links to embedded files, but I have created MIME messages that include inline images.  I have included a function from my script library below.  This function creates a MIME entity and supports inline images (gif and jpeg only).  Since this function is part of my library, it will not run in your environment without a lot of work.  This code is for example purposes only.

Here are some of the main points in the function:
Line #30: Disables automatic MIME conversion.  Important since Notes wants to convert everything to rich text.
Line #42: Removes any MIME already stored in the specified item.  This is not required if you are composing a new memo.  If you need this code, just let me know.
Line #43: Gets or creates a MIME entity for the specified item.  I've added this function below.
Line #45-49: Loads HTML into a stream which is then fed to an HTML MIME part (a child of the main MIME entity).
Line #51-73: Creates the embedded image files as child entities to the main MIME entity.  The content id is set in each file's header in lines 58 & 58.  Line 59 loads the file into a stream, and line 70 loads the stream into the image entity.  To convert this function for your own needs, you will need to know the content types of the files you are embedding (text/html, image/gif, application/pdf, etc).  A list of content types can be found here: http://www.utoronto.ca/webdocs/HTMLdocs/Book/Book-3ed/appb/mimetype.html.  You may also need to experiment with different encoding types.  ENC_IDENTITY_BINARY works for GIF anf JPEG files.

Here is an example of how I would use this function to display two images in a table.

Dim html As String
Dim imageFilePaths As Variant, imageContentIds As Variant
html = "<table><tr><td><img src="cid:img1"></td></tr><tr><td><img src="cid:img2"></td></tr></table>"
imageFilePaths = Split("c:\temp\product_image1.gif,c:\temp\product_image2.jpg", ",")
imageFilePaths = Split("img1,img2", ",")
Call DocSetMimeEntityHtml(doc, "Body", html, imageFilePaths, imageContentIds)
Call doc.Send(False)

If I'm correct, all you need to do is to fix the content and encoding types for your files and wrap the cid in an anchor tag rather than an image tag in the HTML.  Example: <a scr="cid:your_file">click-me</a>.
Public Sub DocSetMimeEntityHtml(doc As NotesDocument, Byval itemName As String, Byval html As String, imageFilePaths As Variant, imageContentIds As Variant)
	' * Sets the html part of a mime entity.
	' * Note: In-line images must be specified in the HTML code using an IMG tag.
	' * For example, if the image's file path is "c:\temp\test.gif", the tag might be:  <img src="cid:test.gif">, 
	' * the imageFilePaths array would contain ["c:\temp\test.gif"], and the imageContentIds array would contain ["test.gif"].
	' * @param doc The NotesDocument that contains the mime entity.
	' * @parma itemName The name of the item used to store the mime entity.
	' * @param html A string containing the HTML code.
	' * @param imageFilePaths An array of strings that contains the full path to all image files referenced by the HTML code.
	' * @param imageContentIds An array of strings that contains the content id (cid) of all image files contained in imageFilePaths.
	' */
	If (TRAP_ERRORS) Then On Error Goto CATCH
	Dim sess As New NotesSession
	Dim stream As NotesStream
	Dim mimeBody As NotesMIMEEntity
	Dim mimeHeader As NotesMimeHeader
	Dim mimeHtml As NotesMIMEEntity
	Dim mimeImageHeader As NotesMimeHeader
	Dim mimeImage As NotesMIMEEntity
	Dim item As NotesItem
	Dim strImagePath As String
	Dim strImageExt As String
	Dim strImageCid As String
	Dim strImageType As String
	Dim iImageIndex As Integer
	Dim bSetImages As Boolean
	Dim bConvertMime As Boolean
	bConvertMime = sess.ConvertMime
	sess.ConvertMime = False
	itemName = Trim(itemName)
	If (itemName = "") Then itemName = "Body"
	bSetImages = False
	If (Isarray(imageFilePaths) Or Isarray(imageContentIds)) Then
		If (Ubound(imageFilePaths) <> Ubound(imageContentIds)) Then
			Error ERR_INVALID_PARAM, MSG_ERR_INVALID_PARAM & "  The image file array does not match the image cid array."
			bSetImages = True
		End If
	End If
	' Create or replace the mime entity.
	Call DocRemoveMimeEntity(doc, itemName)
	Set mimeBody = DocGetMimeEntity(doc, itemName, True)
	' Add the HTML part.
	Set stream = sess.CreateStream()
	Call stream.WriteText(html)
	Set mimeHtml = mimeBody.CreateChildEntity()
	Call mimeHtml.SetContentFromText(stream, {text/html; charset="iso-8859-1"}, ENC_QUOTED_PRINTABLE)
	Call stream.Close()
	' Add the image content.
	If (bSetImages) Then
		For iImageIndex = 0 To Ubound(imageFilePaths)
			' Get the image file path and content id (cid).
			strImagePath = Trim(imageFilePaths(iImageIndex))
			If (strImagePath = "") Then Exit Sub
			strImageCid = Trim(imageContentIds(iImageIndex))
			If (strImageCid = "") Then Exit Sub
			' Get the image context type.
			If (StrContains(strImagePath, ".", True)) Then strImageExt = Strrightback(strImagePath, ".") Else strImageExt = ""
			Select Case Lcase(strImageExt)
			Case "gif":	strImageType = "image/gif"
			Case "jpg":	strImageType = "image/jpg"
			Case Else:	strImageType = "image/gif"
			End Select
			' Add the image part.
			Set mimeImage = mimeBody.CreateChildEntity()
			Set mimeImageHeader = mimeImage.CreateHeader({Content-ID})
			Call mimeImageHeader.SetHeaderVal("<" & strImageCid & ">")
			Call stream.Open(strImagePath)
			Call mimeImage.SetContentFromBytes(stream, strImageType & {;name="} + strImageCid + {"}, ENC_IDENTITY_BINARY)
			Call stream.Close()
	End If
	' Clean up
	sess.ConvertMime = bConvertMime
	Call doc.CloseMIMEEntities(True, itemName)
	Call doc.ReplaceItemValue(itemName & "MimeUpdated", Now)
	Exit Sub
	On Error Goto THROW
	sess.ConvertMime = bConvertMime
	Call doc.CloseMIMEEntities(False, itemName)	
	Call ThrowError("")
End Sub
Public Function DocGetMimeEntity(doc As NotesDocument, Byval itemName As String, Byval create As Boolean) As NotesMimeEntity
	' * Retrieves a MIME Entity from a document.
	' * @param doc The NotesDocument that contains the mime entity.
	' * @parma itemName The name of the item used to store the mime entity.
	' * @param create Specify True to create the entity if it does not already exist.
	' * @return The specified mime entity.
	' */
	If (TRAP_ERRORS) Then On Error Goto CATCH
	Dim sess As New NotesSession()
	Dim entity As NotesMimeEntity
	Dim bConvertMime As Boolean
	bConvertMime = sess.ConvertMime
	sess.ConvertMime = False
	' Get existing mime entity.
	itemName = Trim(itemName)
	If (itemName = "") Then itemName = "Body"
	Set entity = doc.GetMIMEEntity(itemName)
	' Create new mime entity.
	If (entity Is Nothing) Then
		If (create) Then
			If (doc.HasItem(itemName)) Then
				Call DocRemoveMimeEntity(doc, itemName)
			End If
			Set entity = doc.CreateMIMEEntity(itemName)
		End If
	End If
	' Return mime entity.
	Set DocGetMimeEntity = entity
	sess.ConvertMime = bConvertMime
	Exit Function
	sess.ConvertMime = bConvertMime
	Call ThrowError("")
End Function

Open in new window


Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
chuckaliciousAuthor Commented:
I have not had a chance to fully test this, but the response does answer my question, so thanks
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Lotus IBM

From novice to tech pro — start learning today.