Embed Image to Outlook using extended mapi functions - delphi

I need to embed images in an email and preview the email before it is sent in outlook. CDO and Redemption is not an option.
I have tried the following code, but the images just appears as a little block.
procedure AddAttachment(FullFileName: String; Attachments: Outlook2000.Attachments; CID: String);
const
PR_ATTACH_CONTENT_ID = $3712001E;
PR_ATTACH_CONTENT_ID_W = $3712001F; // Unicode
PR_ATTACH_MIME_TAG = $370E001E;
PR_ATTACH_ENCODING = $37020102;
var
IAttach: IMAPIProp;
Prop: PSPropValue;
AAttachment: Outlook2000.Attachment;
FileName: String;
PropValue: TSPropValue;
Prop1: TSPropTagArray;
begin
FileName := ExtractFileName(FullFileName);
Prop := nil;
try
AAttachment := Attachments.Add(FullFileName, olByValue, 1, FileName);
IAttach := AAttachment.MAPIOBJECT as IMAPIProp;
if Assigned(IAttach) then
try
PropValue.ulPropTag := PR_ATTACH_MIME_TAG;
PropValue.Value.lpszA := 'image/jpeg';
HrSetOneProp(IAttach, #PropValue);
PropValue.ulPropTag := PR_ATTACH_CONTENT_ID;
PropValue.Value.lpszA := PAnsiChar(AnsiString(CID));
HrSetOneProp(IAttach, #PropValue);
finally
if Assigned(Prop) then MAPIFreeBuffer(Prop);
IAttach := nil;
end;
except
end;
end;

Questioner hasn't posted his HTML text. I suspect the problem is that his CID urls were malformed - hwoever I have not tested this.
If the Content-ID header is set to this:
Content-Type: image/jpeg
Content-Disposition: inline
Content-ID: afd383988e86ad958709#u
Then the HTML should reference it like so:
<img width="100" height="100" href="cid:afd383988e86ad958709#u" />
In particular, the cid URL must have the prefix "cid:" but the content-id header must not. (A guid is a good choice for a content-id, except that it MUST contain an # symbol. To comply, you can append '#u' to the guid.)
That is sufficent to have the email display correctly at the receiving end. Whether it will make it preview in outlook correctly before sending, I don't know.
You may wish to also see this question:
Embedding images into html email with java mail

Related

How can I put images in the body of a message in delphi 7?

I am working with sending mails through the smtp protocol using the Indy idMessage object in delphi 7 (therefore the Indy version is 9). I was sending messages in html format without problems, but now I would like to embed an image in the body of the message. I saw that it would not be as easy as putting:
<img src='C:\Foo\image.png'>
From what I saw, you have to initialize an IdAttachment and reference it in the html, but I couldn't make it work as such.
Next I leave the code used to create the body of the message
procedure TfmMail.SendMail;
var
IdMensaje: TIdMessage;
smtp: TIdSMTP;
begin
IdMensaje := TIdMessage.Create(nil);
IdMensaje.Clear;
IdMensaje.Body.Clear;
IdMensaje.ContentType := 'text';
IdMensaje.From.Text := 'Title from email';
IdMensaje.Body.Text := 'greeting';
IdMensaje.ContentType := 'text/html';
//<img src='C:\Foo\image.png> -> Don't work
IdMensaje.Body.Text := IdMensaje.Body.Text + '<p> Other text to body </p>;
For Indy 9, you need to:
set the TIdMessage.ContentType property to multipart/related; type="text/html",
add a TIdText object to the TIdMessage.MessageParts property (instead of using TIdMessasage.Body), containing the HTML content. The HTML's <img> tags can refer to each attachment using a cid:<content-id> identifier in their src attribute.
add a separate TIdAttachment object to TIdMessage.MessageParts for each image, and assign a unique Content-ID header assigned to each one.
Try something like this:
procedure TfmMail.SendMail;
var
IdMensaje: TIdMessage;
...
begin
IdMensaje := TIdMessage.Create(nil);
try
...
IdMensaje.ContentType := 'multipart/related; type="text/html"';
with TIdText.Create(IdMensaje.MessageParts, nil) do
begin
ContentType := 'text/html';
Body.Text := '... <img src="cid:myimageid"> ...';
end;
with TIdAttachment.Create(IdMensaje.MessageParts, 'C:\Foo\image.png') do
begin
ContentType := 'image/png';
ContentID := '<myimageid>';
//
// or, if ContentID is not available:
//
// Headers.Values['Content-ID'] := '<myimageid>';
// or:
// ExtraHeaders.Values['Content-ID'] := '<myimageid>';
end;
// use IdMensaje as needed ...
finally
IdMensaje.Free;
end;
end;
You can either point the img tag to an external url (keep in mind that Outlook will block image downloads by default unless the user explicitly clicks on "Download images") or
you can add images as attachments, set their Content-Id MIME header (e.g. to 'xyz') on the attachment MIME part, then refer to that image in the HTML body though that content-id, e.g., <img src="cid:xyz">
Third option would be embedding image data in the img tag, but not all email clients understand that - older versions of Outlook won't render images like that: <img src="data:image/jpeg;base64, LzlqLzRBQ..." />

Delphi, retrieve both visible text and hidden hyperlink when pasting into a delphi application

How can I do that? I've been looking all over the internet to find some clues but failed.
You can click on a link in the browser and copy it and then paste it into a word doc document for example.
I using a tcxGrid with some fields and want to paste this link into the field. The field will show you the text but if you click on it it will open the browser with this link.
I can fix all the later part but I don't know how to extract the text and the link from the clipboard.
Does anyone know how to do it?
I've found an old article that describes how you can do it but the result is not good. I get Chinese text instead of HTML.. see below my test code:
function TForm2.clipBoardAsHTML: string;
var
CF_HTML: UINT;
CFSTR_INETURL: UINT;
URL: THandle;
HTML: THandle;
Ptr: PChar;
begin
CF_HTML := RegisterClipboardFormat('HTML Format');
CFSTR_INETURL := RegisterClipboardFormat('UniformResourceLocator');
result := '';
with Clipboard do
begin
Open;
try
HTML := GetAsHandle(CF_HTML);
if HTML <> 0 then
begin
Ptr := PChar(GlobalLock(HTML));
if Ptr <> nil then
try
Result := Ptr;
finally
GlobalUnlock(HTML);
end;
end;
finally
Close;
end;
end;
end;
Data looks like:
敖獲潩㩮⸱ര匊慴瑲呈䱍〺〰〰〰ㄲര䔊摮呈䱍〺〰〰㈰㐳ള匊慴
and much more.
So something is wrong with my code it looks.. :(
The recommended format CFSTR_INETURL does not exist in the clipboard when takes a copy from Firefox, and Excel so I couldn't get any data using that format.
==================================
Latest test - Retrieve of format names.
procedure TForm2.Button2Click(Sender: TObject);
var
i: integer;
s: string;
szFmtBuf: array[0..350] of PWideChar;
fn: string;
fmt: integer;
begin
Memo1.Clear;
for i := 0 to clipBoard.FormatCount - 1 do
begin
fmt := clipBoard.Formats[i];
getClipBoardFormatName(fmt,#szFmtBuf,sizeOf(szFmtBuf));
fn := WideCharToString(#szFmtBuf);
if fmt >= 49152 then
Memo1.Lines.Add(fmt.ToString+ ' - '+fn);
end;
end;
Finally I made this code work :) but the main question how I'll get the url from the clipboard are still unsolved. :(
If I loop through all found formats I only get garbage from them.
The formats from Firefox looks:
49161 - DataObject
49451 - text/html
49348 - HTML Format
50225 - text/_moz_htmlcontext
50223 - text/_moz_htmlinfo
50222 - text/x-moz-url-priv
49171 - Ole Private Data
It really depends on which format(s) the copier decides to place on the clipboard. It may place multiple formats on the clipboard at a time.
A hyperlink with url and optional text may be represented using either:
the Shell CFSTR_INETURL format (registered name: 'UniformResourceLocator') containing the URL of the link, and the CF_(UNICODE)TEXT format containing the text of the link, if any.
the CF_HTML format (registered name: 'HTML Format') containing whole fragments of HTML, including <a> hyperlinks and optional display text.
The VCL's TClipboard class has HasFormat() and GetAsHandle() methods for accessing the data of formats other than CF_(UNICODE)TEXT (which can be retrieved using the TClipboard.AsText property).
You need to use the Win32 RegisterClipboardFormat() function at runtime to get the format IDs for CFSTR_INETURL and CF_HTML (using the name strings mentioned above) before you can then use those IDs with HasFormat() and GetAsHandle().
You can also enumerate the formats that are currently available on the clipboard, using the TClipboard.FormatCount and TClipboard.Formats[] properties. For format IDs in the $C000..$FFFF range, use the Win32 GetClipboardFormatName() function to retrieve the names that were originally registered with RegisterClipboardFormat().

how can I get email in plain text? I'm using imap indy10 and delphi7

I'm using the following code, but I'm getting html or even base64, I don't know how to decode that in plain text. What is the correct way to read the email content in my delphi app?
var
TheFlags: TIdMessageFlagsSet;
TheUID: string;
nCount: integer;
TheMsg: TIdMessage;
MailBoxName: string;
lacadena:string;
begin
nCount := TheImap.MailBox.TotalMsgs;
for i := 0 to nCount do
begin
TheImap.GetUID(i, TheUID)
TheImap.UIDRetrieveText(TheUID, lacadena);
dbmmoemcontent.text :=lacadena;
end;
end
Try using UIDRetreive() instead of UIDRetrieveText(). UIDRetreive() retrieves the entire email, headers and all, and then decodes it into a TIdMessage. UIDRetrieveText(), on the other hand, retrieves just the raw text of the email body without any headers, and is not decoded in any way other than to convert the raw bytes into a String.

What is the best way of detecting that a Delphi TWebBrowser web page has changed since I last displayed it?

I want to display a 'news' page in a form using Deplhi TWebBrowser. The news page is a simple HTML page which we upload to our website from time to time and may be output from various tools. The display is fine but I'd like to know in my app whether it has changed since I last displayed it, so ideally I'd like to get either its modified date/time or its size / checksum. Precision is not important and ideally should not rely on properties that might fail because 'simple' tools were used to edit the HTML file such as NotePad. Checking on the web there are several document modified java calls but I really dont know where to start with those. I've looked through the numerous calls in Delphi's Winapi.WinInet unit and I see I can fetch the file with HTTP to examine it but that seems like cracking a walnut with a sledgehammer. I also cannot see any file date time functionality which makes me think I'm missing something obvious. I'm using Delphi XE5. In which direction should I be looking please? Thanks for any pointers.
You could use Indy TIdHTTP to send a HEAD request and examine Last-Modified / Content-Length headers.
e.g.:
procedure TForm1.Button1Click(Sender: TObject);
var
Url: string;
Http: TIdHTTP;
LastModified: TDateTime;
ContentLength: Integer;
begin
Url := 'http://yoursite.com/newspage.html';
Http := TIdHTTP.Create(nil);
try
Http.Head(Url);
LastModified := Http.Response.LastModified;
ContentLength := Http.Response.ContentLength;
ShowMessage(Format('Last-Modified: %s ; Content-Length: %d', [DateTimeToStr(LastModified), ContentLength]));
finally
Http.Free;
end;
end;
When the TWebBrowser.DocumentComplete event is fired make a HEAD request and store LastModified and ContentLength variables.
Then periodically make HEAD requests to test for changes (via TTimer for example).
These Header parameters are dependent on the web server implementation, and may not return file system date-time on the server (dynamic pages for example). your server might not result back these parameters at all.
For example, with static HTML pages on IIS, Last-Modified returns the file system last modified date-time, which is what you want.
For dynamic content (e.g. php, asp, .NET etc..), if you control the web-server, you might as well add your own custom HTTP response header on the server side to indicate the file system date-time (e.g. X-Last-Modified) or set the response Last-Modified header to your needs and examine this header on the client side.
If you need to examine/hash the entire HTTP content, you need to issue a GET method: http.Get(URL)
Thanks to a mixture of suggestions and pointers from kobik, David and TLama, I realised that I actually did need a sledgehammer and I finally came up with this solution (and I'm probably not the first, or the last!). I had to read the file contents because this did seem a better way of detecting changes. The code below calls "CheckForWebNewsOnTimer" from a TTimer infrequently and uses Indy to read the news page, make an MD5 hash of its contents and compare that with a previous hash stored in the registry. If the contents change, or 120 days elapses, the page pops up. The code has wrinkles, for example a change to a linked image on the page might not trigger a change but hey, its only news, and text almost always changes too.
function StreamToMD5HashHex( AStream : TStream ) : string;
// Creates an MD5 hash hex of this stream
var
idmd5 : TIdHashMessageDigest5;
begin
idmd5 := TIdHashMessageDigest5.Create;
try
result := idmd5.HashStreamAsHex( AStream );
finally
idmd5.Free;
end;
end;
function HTTPToMD5HashHex( const AURL : string ) : string;
var
HTTP : TidHTTP;
ST : TMemoryStream;
begin
HTTP := TidHTTP.Create( nil );
try
ST := TMemoryStream.Create;
try
HTTP.Get( AURL, ST );
Result := StreamToMD5HashHex( ST );
finally
ST.Free;
end;
finally
HTTP.Free;
end;
end;
function ShouldShowNews( const ANewHash : string; AShowAfterDays : integer ) : boolean;
const
Section = 'NewsPrompt';
IDHash = 'LastHash';
IDLastDayNum = 'LastDayNum';
var
sLastHash : string;
iLastPromptDay : integer;
begin
// Check hash
sLastHash := ReadRegKeyUserStr( Section, IDHash, '' );
Result := not SameText( sLastHash, ANewHash );
if not Result then
begin
// Check elapsed days
iLastPromptDay := ReadRegKeyUserInt( Section, IDLastDayNum, 0 );
Result := Round( Now ) - iLastPromptDay > AShowAfterDays;
end;
if Result then
begin
// Save params for checking next time.
WriteRegKeyUserStr( Section, IDHash, ANewHash );
WriteRegKeyUserInt( Section, IDLastDayNum, Round(Now) );
end;
end;
procedure CheckForWebNewsOnTimer;
var
sHashHex, S : string;
begin
try
S := GetNewsURL; // < my news address
sHashHex := HTTPToMD5HashHex( S );
If ShouldShowNews( sHashHex, 120 {days default} ) then
begin
WebBrowserDlg( S );
end;
except
// .. ignore or save as info
end;
end;

Sending email with Indy 9 with an embedded picture

I am trying to add to a program of mine the capability of sending html email via SMTP with Indy 9. If the program only contains text (the text will be in Hebrew so I need to display it right to left, which means that I am using HTML statements), then the email is sent correctly. My problem lays with embedding pictures into the HTML stream.
The HTML stream will use a command like
<IMG SRC="cid:foo4atfoo1atbar.net" ALT="IETF logo">
Whilst the Indy 10 component TIdAttachmentFile has a ComponentID property whose value has to be set to the value that 'cid' references, I can't find where to set the ComponentID property in Indy 9.
At the moment, the code which deals with adding the picture (whose name is in laPicture.text) looks like this
if laPicture.text <> '' then
with TIdAttachment.Create (email.MessageParts, laPicture.text) do
begin
ContentDisposition:= 'inline';
ContentType:= 'image/jpeg';
DisplayName:= ExtractFileName (laPicture.text);
filename:= ExtractFileName (laPicture.text);
end;
Where do I define the ContentID?
And, although this is a stupid question, how do I know which version of Indy I have?
TIdAttachment derives from TIdMessagePart, which has a public ContentID property. If your installed version of Indy 9 does not have that property, then you are using an outdated version, so use the ExtraHeaders property instead to add a Content-ID header manually.
Have a look at the following blog article on Indy's website for more information about working with HTML emails:
HTML Messages
Update: so, if the HTML says cid:foo4atfoo1atbar.net then you need to do this in your code to match it:
with TIdAttachment.Create (email.MessageParts, laPicture.text) do
begin
...
ContentID := '<foo4atfoo1atbar.net>';
// or this, if you do not have the ContentID property available:
// ExtraHeaders.Values['Content-ID'] := '<foo4atfoo1atbar.net>';
end;
Note that in Indy 9, you have to provide the brackets manually. Indy 10 inserts them for you if they are omitted, eg:
ContentID := 'foo4atfoo1atbar.net';
I found a solution - I didn't need Indy10 not the Content-ID field.
The code which I showed in my question was fine, the problem was probably in the HTML code which displayed the picture. I thought that the "cid" variable had to 'point' to the value of Content-ID; it transpires that it can be set to the name of the file (TIDAttachment.filename), as follows
<img src="cid:' + ExtractFileName (laPicture.text) + '"><br>
The above line gets inserted into the html stream at the appropriate place.
This works for me:
function SendEmail(SMTP: TIdSMTP; CONST AdrTo, AdrFrom, Subject, Body, HtmlImage, DownloadableAttachment: string; SendAsHtml: Boolean= FALSE): Boolean;
VAR MailMessage: TIdMessage;
begin
Result:= FALSE;
Assert(SMTP <> NIL, 'SMTP in NIL!');
MailMessage:= TIdMessage.Create(NIL);
TRY
MailMessage.ConvertPreamble:= TRUE;
MailMessage.Encoding := meDefault;
MailMessage.Subject := Subject;
MailMessage.From.Address := AdrFrom;
MailMessage.Priority := mpNormal;
MailMessage.Recipients.EMailAddresses := AdrTo;
{How to send multi-part/attachment emails with Indy:
www.indyproject.org/2005/08/17/html-messages
www.indyproject.org/2008/01/16/new-html-message-builder-class }
WITH IdMessageBuilder.TIdMessageBuilderHtml.Create DO
TRY
if SendAsHtml
then Html.Text := Body
else PlainText.Text := Body;
{ This will be visible ONLY if the email contains HTML! }
if SendAsHtml AND FileExists(HtmlImage)
then HtmlFiles.Add(HtmlImage);
if FileExists(DownloadableAttachment)
then Attachments.Add(DownloadableAttachment);
FillMessage(MailMessage);
FINALLY
Free;
END;
{ Connect }
TRY
if NOT SMTP.Connected
then SMTP.Connect;
EXCEPT
on E: Exception DO
begin
AppLog.AddError('Cannot connect to the email server.');
AppLog.AddError(E.Message);
end;
END;
{ Send mail }
if SMTP.Connected then
TRY
SMTP.Send(MailMessage);
Result:= TRUE;
EXCEPT
on E:Exception DO
begin
AppLog.AddError('Connected to server but could not send email!');
AppLog.AddError(E.Message);
end;
END;
if SMTP.Connected
then SMTP.Disconnect;
FINALLY
FreeAndNil(MailMessage);
END;
end;
Note: Replace AppLog with your personal logging system or with ShowMessage.
You need of course the libeay32.dll + ssleay32.dll. I would have posted a link to them, but could not find them anymore.

Resources