Sending email with Indy 9 with an embedded picture - delphi

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.

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..." />

iOS Firemonkey: How to send email from iOS App though mail app in Firemonkey Delphi XE7

I am working with iOS App in Firemonkey using Delphi XE7.
Question: I need to send email from my iOS App through the mail app in Firemonkey.
I have seen other old blogs for sending an email but those solutions didn't help me.
Below are the old links which I have tried it, but I couldn't solve.
http://blogs.embarcadero.com/ao/2011/10/04/39133
http://blogs.embarcadero.com/matthiaseissing/2013/05/03/38707/
Kindly let me know some other solutions or samples.
Thanks in advance.
Use the code from the second link you included:
http://blogs.embarcadero.com/matthiaseissing/2013/05/03/38707/
It is for XE4 and you just need a few changes to make it work for XE7:
The StrToNSUrl function has moved to the Macapi.Helpers unit in XE7, so you must add that to your uses clause. In addition the NSStr function is deprecated and so you should use StrToNSStr instead (also from Macapi.Helpers).
Here is a function that puts all the functionality together:
procedure SendEmail(aEmail: string; aSubject: string = ''; aBody: string = '');
var lSharedApplication: UIApplication;
lURL: string;
begin
lURL := 'mailto:'+aEmail;
if (aSubject<>'') or (aBody<>'') then
begin
lURL := lURL+'?subject='+aSubject;
if aBody<>'' then
lURL := lURL+'&body='+aSubject;
lURL := StringReplace(lURL,' ','%20',[rfReplaceAll]); //replace spaces
lURL := StringReplace(lURL,sLineBreak,'%0D%0A',[rfReplaceAll]);//replace linebreaks
end;
lSharedApplication := TUIApplication.Wrap(TUIApplication.OCClass.SharedApplication);
lSharedApplication.openURL(StrToNSUrl(lURL));
end;
Call it like this:
SendEmail('name#email.nowhere','My subject','My body');
Use the TDPFMailCompose class that is included in D.P.F Delphi iOS Native Components
That gives you more options than a mailto: link and you don't have to worry about the encoding. Internally this uses the iOS MFMailComposeViewController class.
Example:
var
Mail: TDPFMailCompose;
begin
Mail := TDPFMailCompose.Create(nil);
if not Mail.MailCompose(Msg.Subject, Msg.Body, False, [Msg.To_], [Msg.CC], [Msg.BCC], [AttachedFileName]) then
MessageDlg('Error sending mail', TMsgDlgType.mtError, [TMsgDlgBtn.mbClose], -1);
end;

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;

Delphi7, Save User's Changes or other User's Information / Notes

In my program, the user completes a form and then presses Submit. Then, a textfile or a random extension file is created, in which all the user's information is written. So, whenever the user runs the application form, it will check if the file, which has all the information, exists, then it copies the information and pastes it to the form. However, it is not working for some reason (no syntax errors):
procedure TForm1.FormCreate(Sender: TObject);
var
filedest: string;
f: TextFile;
info: array[1..12] of string;
begin
filedest := ExtractFilePath(ParamStr(0)) + 'User\Identity\IdentityofMyself.txt';
if FileExists(filedest) then
begin
AssignFile(f,filedest);
Reset(f);
ReadLn(info[1], info[2], info[3], info[4], info[5], info[6], info[7],
info[8], info[9], info[10], info[11], info[12]);
Edit1.Text := info[1];
Edit2.Text := info[2];
ComboBox1.Text := info[3];
ComboBox5.Text := info[4];
ComboBox8.Text := info[4];
ComboBox6.Text := info[5];
ComboBox7.Text := info[6];
Edit3.Text := info[7];
Edit4.Text := info[8];
Edit5.Text := info[11];
Edit6.Text := info[12];
ComboBox9.Text := info[9];
ComboBox10.Text := info[10];
CloseFile(f);
end
else
begin
ShowMessage('File not found');
end;
end;
The file exists, but it shows the message File not found. I don't understand.
I took the liberty of formatting the code for you. Do you see the difference (before, after)? Also, if I were you, I would name the controls better. Instead of Edit1, Edit2, Edit3 etc. you could use eFirstName, eLastName, eEmailAddr, etc. Otherwise it will become a PITA to maintain the code, and you will be likely to confuse e.g. ComboBox7 with ComboBox4.
One concrete problem with your code is this line:
readln(info[1], info[2], info[3], info[4], info[5], info[6], info[7],
info[8], info[9], info[10], info[11], info[12]);
You forgot to specify the file f!
Also, before I formatted your code, the final end of the procedure was missing. Maybe your blocks are incorrect in your actual code, so that ShowMessage will be displayed even if the file exists? (Yet another reason to format your code properly...)
If I encountered this problem and wanted to do some quick debugging, I'd insert
ShowMessage(BoolToStr(FileExists(filedest), true));
Exit;
just after the line
filedest := ...
just to see what the returned value of FileExists(filedest) is. (Of course, you could also set a breakpoint and use the debugger.)
If you get false, you probably wonder what in the world filedest actually contains: Well, replace the 'debugging code' above with this one:
ShowMessage(filedest);
Exit;
Then use Windows Explorer (or better yet: the command prompt) to see if the file really is there or not.
I'd like to mention an another possibility to output a debug message (assuming we do not know how to operate real debugger yet):
{ ... }
filedest := ExtractFilePath(ParamStr(0)) + 'User\Identity\IdentityofMyself.txt';
AllocConsole; // create console window (uses Windows module) - required(!)
WriteLn('"' + filedest + '"'); // and output the value to verify
if FileExists(filedest) then
{ ... }

Embed Image to Outlook using extended mapi functions

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

Resources