HTML Email using Indy

ADUG membership software applications uses Indy to send email notification to members. The actual email code has been modified over the years with help from various members. There is now an outstanding request to enable HTML in emails. This should not be difficult but I cannot immediately see how the HTML content is flagged in the email header. Eg from a Forums HTML post

X-Virus-Scanned: Debian amavis at b2.tigertech.net
Received: from forums.adug.org.au (ec2-54-206-238-124.ap-southeast-2.compute.amazonaws.com [54.206.238.124])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
	 key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256)
	(No client certificate requested)
	by mailb2.tigertech.net (Postfix) with ESMTPSA id 4Y2KhL3q9Pz1nscm
	for <roger@innovasolutions.com.au>; Mon,  2 Dec 2024 15:27:46 -0800 (PST)
Date: Mon, 02 Dec 2024 23:27:46 +0000
From: ADUG Forums <noreply@adug.org.au>
Reply-To: ADUG Forums <noreply@adug.org.au>
To: roger@innovasolutions.com.au
Message-ID: <c863defa-30dd-4ffb-a37e-8a4220cd0422@forums.adug.org.au>
Subject: [ADUG Forums] Summary
Mime-Version: 1.0
Content-Type: multipart/alternative;
 boundary="--==_mimepart_674e4270afa65_2e2a0f3d3b026272d5";
 charset=UTF-8
Content-Transfer-Encoding: 7bit
List-Unsubscribe: <https://forums.adug.org.au/email/unsubscribe/00f57141caefb9b5c4fdd1aaf06734c3b64aaea01f23503df88035994446cacf>
X-Discourse-Post-Ids: 228554
X-Discourse-Topic-Ids: 60535
X-Auto-Response-Suppress: All
Auto-Submitted: auto-generated


----==_mimepart_674e4270afa65_2e2a0f3d3b026272d5
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

A brief summary of [ADUG Forums][1] since 2024-12-01 23:02:37 UTC

1 New Topics

--------------------------------------------------------------------------------

### Popular Topics

The current Email Sender Code is

function TDbConfigurationDataObject.EmailSend(out AError: AnsiString;
  const Subject: string; const MsgBody: string;
  const EmailToAddress: AnsiString; const EmailSenderAddress: AnsiString;
  const EmailReturnAddress: AnsiString; AttachFileNames: array of AnsiString;
  const EmailCopyAddresses: AnsiString;
  const EmailBCCAddresses: AnsiString): Boolean;

var
  Smtp: TIdSMTP;
  MailMsg: TIdMessage;
  MainMessage: TIdText;
  i: Integer;
  // Added to Enable SSL for gmail
  // uses IdUserPassProvider,IdSASLLogin,IdSSLOpenSSL,IdExplicitTLSClientServerBase
  PasswordProvider: TIdUserPassProvider;
  SaslLogin: TIdSaslLogin;
  SslIoHandler: TIdSSLIOHandlerSocketOpenSSL;

begin
  AError := '';
  if GBLSMTPServerHostAddress = '' then
    raise Exception.Create
      ('You Need to set up the GBLSMTPServerHostAddress Variable');

  Result := false;
  if gblHoldEmailIfDebug then
  begin
    Result := True;
    Exit;
  end;

  // Added to Enable SSL for gmail
  PasswordProvider := nil;
  SaslLogin := nil;
  SslIoHandler := nil;

  try
    Smtp := TIdSMTP.Create(nil);
    MailMsg := TIdMessage.Create(nil);
    try
      Smtp.Host := GBLSMTPServerHostAddress;
      Smtp.MailAgent := ccMailAgent;
      Smtp.Port := GblSMTPPort;
      if GBLSMTPServerPasswordEncrpted <> '' then
        if GBLSMTPServerSenderAccount <> '' then
        begin
          Smtp.Username := GBLSMTPServerSenderAccount;
          Smtp.Password := TCgiAccessObj.DecryptEmailPassword
            (GBLSMTPServerPasswordEncrpted);
          // Added to Enable SSL for gmail
          if GblSMTPUseSSL then
          begin
            PasswordProvider := TIdUserPassProvider.Create(nil);
            // neither is this
            SaslLogin := TIdSaslLogin.Create(nil); // or this
            SslIoHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
            SslIoHandler.SSLOptions.Method := sslvTLSv1_2;
            SslIoHandler.SSLOptions.SSLVersions := [sslvTLSv1_2];
            SslIoHandler.SSLOptions.VerifyMode := [];
            SslIoHandler.SSLOptions.VerifyDepth := 2;

            Smtp.IoHandler := SslIoHandler;
            Smtp.UseTLS := utUseImplicitTLS;
            Smtp.AuthType := satDefault;
          end;

        end;

      MailMsg.Subject := Subject;
      MailMsg.Recipients.EMailAddresses := EmailToAddress;
      MailMsg.From.Text := EmailSenderAddress;
      if EmailReturnAddress <> '' then
        MailMsg.ReplyTo.EMailAddresses := EmailReturnAddress
      else
        MailMsg.ReplyTo.EMailAddresses := EmailSenderAddress;
      MailMsg.CCList.EMailAddresses := EmailCopyAddresses;
      MailMsg.BccList.EMailAddresses := EmailBCCAddresses;
      MainMessage := TIdText.Create(MailMsg.MessageParts, nil);
      MainMessage.Body.Text := MsgBody;
      if CompressedUnicode(MsgBody) = '' then
      begin
        MailMsg.ContentTransferEncoding := '8bit';
        MainMessage.ContentType := 'text/plain; charset=UTF-8';
      end;
      // CharSet := 'UTF-8';
      // https://stackoverflow.com/questions/9844250/not-able-to-send-utf-8-email-using-delphi-indy
      for i := 0 to high(AttachFileNames) do
      begin
        if (AttachFileNames[i] <> '') and FileExists(AttachFileNames[i]) then
          TIdAttachmentFile.Create(MailMsg.MessageParts, AttachFileNames[i]);
      end;
      Smtp.Connect;
      try
        Smtp.Send(MailMsg);
      finally
        Smtp.Disconnect;
      end;
      Result := True;
    except
      on E: Exception do
        try
          AError := E.Message;
          LogALine(FormatDateTime('dd mmmm yyyy hh:nn', Now));
          LogALine('Email Error: Server:' + GBLSMTPServerHostAddress + ':' +
            E.Message);
          LogALine('Subject::' + MailMsg.Subject + '    To:' +
            MailMsg.Recipients.EMailAddresses);
          LogALine('MailMsg.ReplyTo.EMailAddresses =' +
            MailMsg.ReplyTo.EMailAddresses);
          LogALine('MailMsg.From.Text =' + MailMsg.From.Text);
          Result := false;
          if Assigned(FEmergengyEmailFunction) then
          begin
            FEmergengyEmailFunction('Problem At ADUG Email',
              'Subject::' + MailMsg.Subject + '    To:' +
              MailMsg.Recipients.EMailAddresses, EmailManagementMessages,
              EmailSenderAddress, EmailReturnAddress, []);
            Result := FEmergengyEmailFunction(Subject, MsgBody, EmailToAddress,
              EmailSenderAddress, EmailReturnAddress, AttachFileNames,
              EmailCopyAddresses, EmailBCCAddresses);
          end;
        except
        end;
    end;

    try
      MailMsg.Free;
    except
    end;
    Smtp.Free;
    // Added to Enable SSL for gmail
    PasswordProvider.Free;
    SaslLogin.Free;
    SslIoHandler.Free;
  except
    on E: Exception do
      Result := false;
  end;
end;

I think the bit of code which needs modification is

      MailMsg.Subject := Subject;
      MailMsg.Recipients.EMailAddresses := EmailToAddress;
      MailMsg.From.Text := EmailSenderAddress;
      if EmailReturnAddress <> '' then
        MailMsg.ReplyTo.EMailAddresses := EmailReturnAddress
      else
        MailMsg.ReplyTo.EMailAddresses := EmailSenderAddress;
      MailMsg.CCList.EMailAddresses := EmailCopyAddresses;
      MailMsg.BccList.EMailAddresses := EmailBCCAddresses;
     MainMessage := TIdText.Create(MailMsg.MessageParts, nil);
      MainMessage.Body.Text := MsgBody;
      if CompressedUnicode(MsgBody) = '' then
      begin
        MailMsg.ContentTransferEncoding := '8bit';
        MainMessage.ContentType := 'text/plain; charset=UTF-8';
      end;
      // CharSet := 'UTF-8';
      // https://stackoverflow.com/questions/9844250/not-able-to-send-utf-8-email-using-delphi-indy

You are right I could go and research the email standards but this is ADUG code and I am hoping some ADUG community member is already across the issue.

It’s this one, inside the “attached” part(s):

Content-Type: text/plain

So, what you basically have here is:

  1. Plain text header, saying that:
Mime-Version: 1.0
Content-Type: multipart/alternative;
 boundary="--==_mimepart_674e4270afa65_2e2a0f3d3b026272d5";
 charset=UTF-8

`Content-Transfer-Encoding: 7bit```

``

  1. And then these individual parts, each with its own encoding (and possibly in BASE64 too).

Alex

Hi Roger,

Here a slightly tidied up code fragment I wrote 20 years ago in Delphi 7. It may need some changes though, I haven’t tested if it compiles, but it should give you an idea what to do. It allowed creating a HTML email message that included embedded images.


  msg.Clear;
  msg.Body.Text     := PageProducer1.Content;
  msg.From.Name     := 'From name';
  msg.From.Address  := 'from@emailaddress';
  msg.Subject       := 'Subject';
  msg.ContentType   := 'multipart/mixed';

  txtpart := TIdText.Create(msg.MessageParts);
  txtpart.ContentType := 'text/plain';
  txtpart.Body.Text := '<Plain text msg goes here>';

  htmpart := TIdText.Create(msg.MessageParts, msg.Body);
  htmpart.ContentType := 'text/html';

  addr         := msg.Recipients.Add;
  addr.Name    := 'Some name';
  addr.Address := 'Someone@someaddress.com';
  for i := 0 to imgList2.Count-1 do
   begin
     attach := TIdAttachment.Create(msg.MessageParts, imgList2.Strings[i]);
     attach.ContentType := 'image/jpeg';
     attach.Headers.Values['Content-Disposition'] := 'inline';
     attach.ExtraHeaders.Values['Content-ID']          := ExtractFilename(imgList2.Strings[i]);
     attach := nil;
   end;

I did this a long time ago.

I build my HTML email into a TStringList being careful not to exceed line width limit for html emails.

Then I build these parts which includes any number of attachments.

The thing to note is the hierarchy of the parent parts.

I use TWebBroswer to strip the html out and convert to plain test which obeys line feeds.

emailBody1 := TIdText.Create(IdMessage1.MessageParts, nil);
emailBody1.ContentType := ‘multipart/related; type=“multipart/alternative”’;
emailBody1.ParentPart := -1;

emailBodyA := TIdText.Create(IdMessage1.MessageParts, nil);
emailBodyA.ContentType := ‘multipart/alternative’;
emailBodyA.ParentPart := 0;

emailBody2 := TIdText.Create(IdMessage1.MessageParts, nil);
emailBody2.Body.Text := HtmlToText(InvNotice.Text);
emailBody2.ContentType := ‘text/plain’;
emailBody2.ParentPart := 1;

emailBody3 := TIdText.Create(IdMessage1.MessageParts, nil);
emailBody3.Body.Text := InvNotice.Text;
emailBody3.ContentType := ‘text/html’;
emailBody3.ParentPart := 1;

if (FattachList.Count>0) then begin
for i:=0 to FattachList.Count-1 do begin
with TIdAttachmentFile.Create(IdMessage1.MessageParts, FattachList.Strings[i]) do begin
ContentType := GetMIMETypeFromFile(FattachList.Strings[i]);
FileName := FattachList.Strings[i];
ParentPart := -1;
end;
end;
end;

IdMessage1.ContentType := ‘multipart/mixed’;
IdSMTP1.Send(idMessage1);

Thanks Guys
That certainly gives me something to work with.

When I get it going I will probably be back for recommendations on how to compose HTML Emails.

Ok I now have working code that sends HTML emails.

Issues (Minor)
Endcoding Over the HTML provided
Choosing quoted-printable encoding replaces = with =3D in the email HTML and inserts an = and line breaks if a line is “too long”
Adding htmpart.ContentTransfer:= ‘7bit’; to the HTML part removes the default “quoted-printable” encoding.
The email clients seem to handle the “=3D” but it probably does not make sense to wrap HTML code at printer boundaries.

What is the effect of Parent Part values?
In the advice from Alan McDonald he seemed to suggest adding a couple of extra empty partitions and coding “Parent Part” values of .1, 0, and 1 in the various partitions.

The Big Issue
The Big Issue I now have is that I am unable to reference files included in the email as additional partitions from the HTML code in order to include email specific content as distinct from images available as a URL.

Looking at the HTML generated by email clients I felt

<img src="cid:myfilename.jpg" alt="image" width="3120" height="4160"> 

along with a partition header on the file of

Content-Type: image/jpeg;
	name="myfilename.jpg"
Content-Transfer-Encoding: base64
Content-Disposition: inline;
	filename="myfilename.jpg"
Content-ID: <myfilename.jpg>

Should do the Job but obviously I am missing something

@RogerConnell Do you have the image data in your email?

For example, this email …

Looks like :

This is a multi-part message in MIME format.
--------------1UDu5YGh84ql4FkVQOJJ8BFs
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit

test email … ADUG logo.

 &^^$^^**())())__*^^%$

#$%^%&(*)()__)+

--------------1UDu5YGh84ql4FkVQOJJ8BFs
Content-Type: multipart/related;
boundary=“------------l0K0VEsStyNZgAjUonSLAxOx”

--------------l0K0VEsStyNZgAjUonSLAxOx
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit

< !DOCTYPE html>
< html >
< head>

<meta http-equiv="content-type" content="text/html; charset=UTF-8">

< /head>
< body>
< p>test email … ADUG logo.

< /p>
< p>Â &^^$^^**())())^^%$

< /p>
< p >
< img src= “cid:part1.SgTd42ys.0M0vbTPk@bigpond.com” alt=“”>
< / p>
< p>#$%^%&(
)_()
)_+


< p>


< /body>
< /html>
--------------l0K0VEsStyNZgAjUonSLAxOx
Content-Type: image/png; name=“BQP2IByEFK1QhSSv.png”
Content-Disposition: inline; filename=“BQP2IByEFK1QhSSv.png”
Content-Id: part1.SgTd42ys.0M0vbTPk@bigpond.com
Content-Transfer-Encoding: base64

iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAgAElEQVR4Xu1dB5hURbY+kxkG
GGAQyaJiQEQRFJXHKpJEUQHfLiZQMogJzErQNSHCCq6KmBAT6q4BfQoChjWLqIsSBBHJQXKa
yDDz/r/q1u3q27cnMM3M0Hbx8c1M971169Zf59TJFddmyNBCibWonYG4GMBRi616sRjA0Y1v
DOAoxzcGcAzgaJ+BKH+/2B4cAzjKZyDKXy9GwTGAo3wGovz1YhQcAzjKZyDKXy9GwTGAo2sG

… [ 154 Lines in total here ]

/pzF5v6mRhf0Fz8Jfl6xztEQ3Bg9kgjDCg0dFKzoQoxEiyjAkRhQrI/IzkAM4MjOZ6XrLQZw
pYMksgOKARzZ+ax0vcUArnSQRHZAMYAjO5+VrrcYwJUOksgOKAZwZOez0vUWA7jSQRLZAcUA
jux8VrreYgBXOkgiO6AYwJGdz0rXWwzgSmY2uTgAAAASSURBVAdJZAcUAziy81npevt/hSnP
LaBs5qUAAAAASUVORK5CYII=

--------------l0K0VEsStyNZgAjUonSLAxOx–

--------------1UDu5YGh84ql4FkVQOJJ8BFs–

They should just be other parts:

if (FattachList.Count>0) then begin

for i:=0 to FattachList.Count-1 do begin

with TIdAttachmentFile.Create(IdMessage1.MessageParts, FattachList.Strings[i]) do begin

ContentType := GetMIMETypeFromFile(FattachList.Strings[i]);

FileName := FattachList.Strings[i];

ParentPart := -1;

end;

end;

end;

Alan McDonald

Worimi Country

I used to use the cid method of embedding images. But the myriad number of email clients about all do something different with the embedded image. It was never really satisfying to see the images thrown out as attachments anyway.

The url method provides a more consistent result and offers recipients with the typical security of choosing to display or not display images from the (trusted) source.

Gmail will intercept the image and show it but only via their server, not the source.

Outlook will just block images unless the recipient chooses to trust them.

Alan McDonald

Thanks Alan
based on your comment I think I can stop trying to support embedded images. I do not need to waste more time on this.

If you do go the url image method. Give it the dimensions that you want it displayed at so at least when recipients have images blocked, the image area is fixed and represents your layout correctly even though it will be an empty box.

Alan McDonald