[Demo] Sending HTML email. Saving MIME-encoded files.

Demos, code samples. Only questions related to the existing topics are allowed here.
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

[Demo] Sending HTML email. Saving MIME-encoded files.

Post by Sergey Tkachenko »

This example shows how to save HTML file with images in one file:

https://www.trichview.com/support/files/mime.zip

This file contains 3 demos:
  • Indy - sending HTML emails using Indy
  • Indy.old - sending HTML emails using Indy but preparing email body manually. If you use Delphi XE6 or older, this demo requires DMime, see below. For Delphi XE7 and newer, it uses the standard functions.
  • SaveFile - saving MIME-encoded HTML+images (MHT file); requires DMime.
Note: for Delphi X6 and older, the Delphi Inspiration's DIMime unit is used for base64 encoding.
Its installation is included in the zip file above.
Their web page is http://www.yunqa.de/delphi/doku.php/products/mime/
Starting from XE7, Delphi includes its own functions for base64 encoding.
[+] History of updates
2007-Sep-23: OnSaveImage2 is changed (see below), new demo for sending HTML e-mail using Indy.
2008-Dec-11: updated version of DMime is included. Delphi\SaveFile demo can be used in all versions of Delphi, including 2009. Delphi\Indy is generally ok in D2009 too, but since TIdSMTP properties are changed, it should be corrected. C++Builder demo was not updated yet. On request.
2012-Mar-29: the file saving demo is corrected
2012-May-9: new demo using the build-in Indy features to create HTML email.
2018-Apr-16: compatibility with TRichView 17.3; newer version of DMime; a demo for C++Builder XE7 and newer instead of C++Builder 6, see viewtopic.php?f=3&t=11&p=34690#p34690
[+] Old versions
https://www.trichview.com/support/files/mime-old.zip - for TRichView 17.2 and older
Last edited by Sergey Tkachenko on Wed May 09, 2012 10:17 am, edited 8 times in total.
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

This demo has wrong comment:
{ Actually, this demo converts all pictures to Jpegs, so only TJpegImage can
occur here. But you can use RV_RegisterHTMLGraphicFormat to allow other
graphic formats in HTML }

Actually, this demo saves all images in formats as they are. It uses OnSaveImage2 event, and images are passed to this event without conversion to jpegs. For the purposes of e-mail saving, you should convert images to jpegs (or png, or gifs). Change the code for this event to (and add CRVData in uses):

Code: Select all

procedure TForm1.RichViewEdit1SaveImage2(Sender: TCustomRichView;
  Graphic: TGraphic; SaveFormat: TRVSaveFormat; const Path,
  ImagePrefix: String; var ImageSaveNo: Integer; var Location: String;
  var DoDefault: Boolean);
var gr: TGraphic;
    bmp: TBitmap;
begin
  if SaveFormat<>rvsfHTML then
    exit;
  if not (Graphic is TJPEGImage) and not RV_IsHTMLGraphicFormat(Graphic) then begin
    bmp := TBitmap.Create;
    try
      bmp.Assign(Graphic);
    except
      bmp.Width := Graphic.Width;
      bmp.Height := Graphic.Height;
      bmp.Canvas.Draw(0,0, Graphic);
    end;
    gr := TJPEGImage.Create;
    gr.Assign(bmp);
    bmp.Free;
    end
  else
    gr := Graphic;

  Location := Format('image%d.%s', [ImageSaveNo, GraphicExtension(TGraphicClass(gr.ClassType))]);
  inc(ImageSaveNo);
  with HTMLImages.Add as THTMLImageItem do
  begin
    gr.SaveToStream(Stream);
    Name := Location;
    ContentType := GetImageContentType(gr);
  end;
  Location := 'cid:'+Location;
  DoDefault := False;
  if gr<>Graphic then
    gr.Free;
end;
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

How to send this e-mail?

Post by Sergey Tkachenko »

How to send TRichView document as formatted e-mail

This example uses TNMSMTP component

In the same demo, add the new function returning MIME-encoded document as string.
As you can see, there are 2 main differences comparing to the original file saving procedure from the demo:
1) headers are not included
2) not only HTML+images, but a plain text alternative is included.

Code: Select all

uses RVUni;

function TForm1.GetEMail: String;
var Stream: TStringStream;
    Stream2: TMemoryStream;
    ws: WideString;
    s, s2: String;
    boundary: String;
    i: Integer;
begin

  // saving text
  HTMLImages.Clear;
  Stream2 := TMemoryStream.Create;
  RichViewEdit1.SaveTextToStreamW('', Stream2, 80, False, False);
  Stream2.Position := 0;
  SetLength(ws, Stream2.Size div 2);
  if Stream2.Size<>0 then
    Stream2.ReadBuffer(Pointer(ws)^, Stream2.Size);
  s2 := MimeEncodeString(Utf8Encode(ws));
  Stream2.Free;

  // saving HTML
  Stream := TStringStream.Create('');
  RichViewEdit1.SaveHTMLToStreamEx(Stream, '', 'Web Archive Demo', '', '', '', '',
    [rvsoUseCheckpointsNames, rvsoUTF8]);
  s := MimeEncodeString(Stream.DataString);
  Stream.Free;
  // now s contains HTML file without images, base64-encoded. saving it in MIME
  boundary := '----=_BOUNDARY_LINE_';

  Result :=
       'This is a multi-part message in MIME format.'+CRLF+CRLF+
       '--'+boundary+CRLF+
       'Content-Type: text/plain;'+CRLF+
       #9'charset="utf-8"'+CRLF+
       'Content-Transfer-Encoding: base64'+CRLF+CRLF+
       s2+CRLF+CRLF+
       '--'+boundary+CRLF+
       'Content-Type: text/html;'+CRLF+
       #9'charset="utf-8"'+CRLF+
       'Content-Transfer-Encoding: base64'+CRLF+CRLF+
       s+CRLF;

  // saving images
  for i := 0 to HTMLImages.Count-1 do begin
    s2 := CRLF+
          '--'+boundary+CRLF;
    SetLength(s, HTMLImages[i].Stream.Size);
    HTMLImages[i].Stream.Position := 0;
    HTMLImages[i].Stream.ReadBuffer(PChar(s)^, Length(s));
    s := MimeEncodeString(s);
    s2 := s2+'Content-Type: '+HTMLImages[i].ContentType+CRLF+
      #9'Name="'+HTMLImages[i].Name+'"'+CRLF+
      'Content-Transfer-Encoding: base64'+CRLF+
      'Content-ID: <'+HTMLImages[i].Name+'>'+CRLF+CRLF+
      s+CRLF;
    Result := Result+s2;
  end;
  Result := Result+CRLF+'--'+boundary+'--'+CRLF;
  HTMLImages.Clear;
end;
Sending:

Code: Select all

    NMSMTP1.Host := ...;
    NMSMTP1.Port := ...;
    NMSMTP1.UserID := ...;
    NMSMTP1.Connect;

    NMSMTP1.PostMessage.FromAddress := ...;
    NMSMTP1.PostMessage.FromName := ...;
    NMSMTP1.PostMessage.ToAddress.Text := ...;
    NMSMTP1.PostMessage.ToCarbonCopy.Text := '';
    NMSMTP1.PostMessage.ToBlindCarbonCopy.Text := '';
    NMSMTP1.PostMessage.Body.Text := GetEMail;

    NMSMTP1.PostMessage.Attachments.Text := '';
    NMSMTP1.PostMessage.Subject := 'HTML Test';
    NMSMTP1.PostMessage.LocalProgram := 'Demo HTML Mailer';
    NMSMTP1.PostMessage.Date := '';
    NMSMTP1.PostMessage.ReplyTo := ...;
    NMSMTP1.SendMail;
The main trick is in adding headers in NMSMTP1.OnSendStart:

Code: Select all

procedure TForm1.NMSMTP1SendStart(Sender: TObject);
begin
  NMSMTP1.FinalHeader.Add('MIME-Version: 1.0');
  NMSMTP1.FinalHeader.Add('Content-Type: multipart/alternative;');
  NMSMTP1.FinalHeader.Add(#9'boundary="----=_BOUNDARY_LINE_"');
end;
That's all.
Please do not abuse this feature.
Last edited by Sergey Tkachenko on Sat Nov 04, 2006 9:32 am, edited 3 times in total.
alogrep
Posts: 52
Joined: Fri Oct 27, 2006 5:25 pm

Post by alogrep »

Thanks Sergey
This looks a great neat feature.
1. what do you mean by "please do not abuse this feature"?
2. There is a little glitch: the mail body looks exactly as it should, however the 'Attachment' column in Outlook Express shows the symbol of the attachment, although there is no attachment: just the little clip.
I verified in SendStart, that PostMessaage.Attachments.Text=''.
Why does it show the little clip?
If i inspect SMTP1.PostMessage.Attachemtents i see (0, nil, nil, 0, 0, False, dupIgnore, nil, nil)
THANKS
Enrico
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

1. I asked to not use it for bad stuff
2. Strange, I cannot see the clip icon in my OE for such messages. Please send e-mail to me.
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

How to send this e-mail?

Post by Sergey Tkachenko »

How to send TRichView document as formatted e-mail - 2

This example uses Indy components: TIdSMTP and TIdMessage.

The function TForm1.GetEMail is the same as above.

Sending (assuming that SMTP server requires authorization):

Code: Select all

    IdMessage1.Clear;

    IdMessage1.From.Address := ...;
    IdMessage1.From.Name := ...;

    with IdMessage1.Recipients.Add do begin
      Address := ...;
      Name := ...;
    end;

    with IdMessage1.ReplyTo.Add do begin
      Address := ...;
      Name := ...;
    end;

    IdMessage1.Body.Text := GetEMail;
    IdMessage1.Subject := 'HTML Test';

    IdMessage1.ExtraHeaders.Add('MIME-Version: 1.0');
    IdMessage1.ExtraHeaders.Add('Content-Type: multipart/alternative;');
    IdMessage1.ExtraHeaders.Add(#9'boundary="----=_BOUNDARY_LINE_"');

    IdSMTP1.MailAgent := 'Demo HTML Mailer';
    IdSMTP1.AuthenticationType := atLogin;
    IdSMTP1.Host := ...;
    IdSMTP1.Port := ...;
    IdSMTP1.UserId := ...;
    IdSMTP1.Password := ...;

    IdSMTP1.Connect;
    IdSMTP1.Send(IdMessage1);
alogrep
Posts: 52
Joined: Fri Oct 27, 2006 5:25 pm

Post by alogrep »

Sent it using the program that uses your code. Did you receive it?
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

Yes, and I cannot see the attachment icon. I use Outlook Express 6.00.2800.1123
alogrep
Posts: 52
Joined: Fri Oct 27, 2006 5:25 pm

Post by alogrep »

Strange, I use the exact same OE. I did a lot of reserach on the web, none of the conditions described applies (OE could show the false Attachment icon, for example if there is a "begin " in the body of the message).
Anyway, I realize you cannot do much more. Thanks. One quetion: in this forum, it is only you that answers questions? Do the users share their tips and help with others?
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

Answering questions here is a part of my work. Others may do it as a hobby :)
In this forum ("Examples, Demos") only I can create a new topic.
Other users can post their examples, demos, help and tips in "Support" forum. If they are interesting for other users, I'll repost them here.
ohm0485
Posts: 2
Joined: Thu Nov 09, 2006 8:25 am

access violation

Post by ohm0485 »

I try to run your demo and occur access violation when call this method

RichViewEdit1.SaveHTMLToStreamEx(Stream, '', 'Web Archive Demo', '', '', '', '', [rvsoUseCheckpointsNames, rvsoUTF8]);


Thanks.
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

What version of TRichView and Delphi?

Please compile the demo with stack frames and without optimization (Compiler tab in the project options).
Run the demo. Which line of TRichView (or the demo (probably the problem is in event)) code generates the error?
parkheaven
Posts: 1
Joined: Fri Apr 20, 2007 2:51 am
Location: Lelystad The Netherlands

Mime encoded formatted e-mail

Post by parkheaven »

Dear Sergey,

The technique you use for sending mime encoded formatted e-mail
(see http://www.trichview.com/forums/viewtopic.php?t=11) works excellent! But how can I combine this technique with sending a 'normal' attachment?

TIdAttachment.Create(IdMessage.MessageParts, s) does not work together with the ExtraHeaders addition.

We are integrating an email client into a CRM program and experimenting with the TRichView trial version. If we can use this technique and send real attachments too, we are definitely in!

Hope you or anyone else can help...
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

You cannot use Indy's MIME features. Indy must think that this is a plain text e-mail.
Ok, let's assume that we have Attachments: TStringList with paths to attached files.

Modify GetEmail function (see above).
Add Stream3: TFileStream to vars.
Add the code below to the end of this function
(before

Code: Select all

  Result := Result+CRLF+'--'+boundary+'--'+CRLF; 
  HTMLImages.Clear; 
end;
)
Code to add:

Code: Select all

// saving attached files
  for i := 0 to Attachments.Count-1 do begin 
    s := ExtractFileName(Attachments[i]);
    s2 := CRLF+ '--'+boundary+CRLF+
   'Content-Type: application/octet-stream; name="'+s+'"'+CRLF+
   'Content-Disposition: attachment; filename="'+s+'"+CRLF+
   'Content-Transfer-Encoding: base64+CRLF+CRLF;
   Stream3 := TFileStream.Create(Attachments[i], fmOpenRead);
   try
     SetLength(s, Stream3.Size);
     Stream3.ReadBuffer(PChar(s)^, Stream3.Size);
   except
     s := '(Cannot load file)';
   end;
   Stream3.Free;
   s := MimeEncodeString(s); 
   Result := Result + s2+s+CRLF;
end;
PS: I wrote this code in browser, not tested.
Possible problems with this code: if file names have non-English characters, they must be encoded somehow
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

Update: http://www.trichview.com/support/files/mime.zip contains demo for sending HTML e-mail with Indy.

Updated 2008-Jan-4:
E-mail structure used with the SendEmail project is changed. E-mails sent by the previous version were read by Outlook Express, but other mailers had problems. Now documents generated by GetEMail have a nested structure: (((text, HTML), image, image, ...), file, file, ...). Only SendEmail demo was changed (for file saving, text and attachments are not supported, so nested structure was not required).
Post Reply