Page 1 of 3

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

Posted: Sun Aug 28, 2005 11:27 am
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

Posted: Sun Oct 29, 2006 12:28 pm
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;

How to send this e-mail?

Posted: Fri Nov 03, 2006 7:45 pm
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.

Posted: Fri Nov 03, 2006 10:19 pm
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

Posted: Sat Nov 04, 2006 9:23 am
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.

How to send this e-mail?

Posted: Sat Nov 04, 2006 9:27 am
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);

Posted: Mon Nov 06, 2006 7:56 pm
by alogrep
Sent it using the program that uses your code. Did you receive it?

Posted: Mon Nov 06, 2006 9:42 pm
by Sergey Tkachenko
Yes, and I cannot see the attachment icon. I use Outlook Express 6.00.2800.1123

Posted: Tue Nov 07, 2006 7:00 pm
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?

Posted: Tue Nov 07, 2006 9:14 pm
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.

access violation

Posted: Fri Nov 10, 2006 2:49 am
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.

Posted: Fri Nov 10, 2006 10:35 pm
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?

Mime encoded formatted e-mail

Posted: Fri Apr 20, 2007 3:12 am
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...

Posted: Fri Apr 20, 2007 4:34 pm
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

Posted: Sun Sep 23, 2007 5:23 pm
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).