Indy9 does not correctly send HTML when attachments are added - delphi

We have a windows service that's written in Delphi 7 that sends out emails containing HTML. This was working perfectly before I added attachments. After adding attachments, the HTML no longer shows as HTML but now shows as plain Text.
After some researching, I found that I have to set the mail content type to multipart/mixed however this does not seem change anything. I also found several articles showing that I have to use MessageParts when adding multiple content types like the following:
For the attachments I have the following code that works fine.
for I := 0 to slAttachments.Count -1 do
begin
with TIdAttachment.Create(MailMessage.MessageParts, slAttachments[I]) do
begin
ContentType := 'application/pdf';
end;
end;
Using TIdText as shown below leaves the body of the email empty after sending. Debugging shows that sMsg contains the correct HTML but it does not get sent with the email.
MailText := TIdText.Create(MailMessage.MessageParts, nil);
MailText.ContentType := 'text/html';
MailText.Body.Text := sMsg;
If I directly set the MailMessage body, the html shows up as plain text.
MailMessage.Body.Text := sMsg;
Full Code:
//setup mail message
MailMessage.From.Address := msFromAddress;
MailMessage.Recipients.EMailAddresses := sToAddress;
MailMessage.Subject := sSubject;
MailMessage.ContentType := 'multipart/mixed';
// Add Attachments
for I := 0 to slAttachments.Count -1 do
begin
with TIdAttachment.Create(MailMessage.MessageParts, slAttachments[I]) do
begin
ContentType := 'application/pdf';
end;
end;
// Add HTML
MailText := TIdText.Create(MailMessage.MessageParts, nil);
MailText.ContentType := 'text/html';
MailText.Body.Text := sMsg;
How can I send attachments and have the HTML shown at the same time? The same code works correctly in Delphi 10. I'm not able to upgrade this project to Delphi 10 due to some dependencies. Indy also cannot be upgraded due to breaking changes.

It seems to be a bug in Indy9 where the first TIdText gets ignored when attachments are added. Adding a plain TIdText seems to have fixed this issue.
Add a plain text TIdText.
Add the HTML TIdText you with the html body you require.
Add the attachments.
Tested and working code below. The first TIdText that contains plain text seems to be ignored however when removing it the html gets ignored.
// Set content type for the mail message
MailMessage.ContentType := 'multipart/mixed';
// Plain text body
With TIdText.Create(MailMessage.MessageParts, nil) do
begin
ContentType := 'text/plain';
Body.Text := 'This gets ignored for some reason'; // Doesn't have to be empty
end;
// HTML (HTML body to send)
With TIdText.Create(MailMessage.MessageParts, nil) do
begin
ContentType := 'text/html';
Body.Text := '<h1>Hello World</h1>';
end;
// Attachments
for I := 0 to slAttachments.Count -1 do
begin
with TIdAttachment.Create(MailMessage.MessageParts, slAttachments[I]) do
begin
ContentType := 'application/pdf';
end;
end;
// Send the mail
smtp.Send(MailMessage);

Related

Indy. sending Attach pdf by stream

I'm having trouble sending attachments via stream.
I use Indy 10.6.2 and Delphi Berlin.
The mail consists of html with attached images, plus one or more PDF files inserted directly from the database.
I don't get any errors in the process.
Mail is sent seamlessly, but attached PDFs are not received.
I look forward to comments
Msg := TIdMessage.Create(self);
try
Msg.ContentType := 'multipart/mixed';
Msg.From.Name := FromName;
Msg.From.Address := FromAddress;
Msg.Priority := mpHigh;
Msg.Subject := Asunto;
with TIdMessageBuilderHtml.Create do
try
if FMsgTxtPlnPac <> '' then
PlainText.Text := FMsgTxtPlnPac;
if FMsgHtmlPac <> '' then
begin
Html.Text := FMsgHtmlPac;
n := 0;
for s in FMsgHTMLFiles.Split([',']) do
begin
n := Succ(n);
c := 'img' + inttostr(n);
HTMLFiles.Add(s, c); // cid de imagen en HTML(cid:img1, cid:img2...)
end;
end;
if AttachFiles <> '' then
for s in AttachFiles.Split([',']) do
Attachments.Add(s);
// Attach from DB
while not dm.qryBlb.Eof do
begin
Attach := TIdAttachmentMemory.Create(Msg.MessageParts);
Attach.ContentType := 'application/pdf';
Attach.FileName := dm.qryBlb.FieldByName('nombre_archivo').AsString;
Attach.LoadFromStream(dm.GetDataBlbStrm('DATA_TXT')); // this ok in attach.datastream.size
Attach.CloseLoadStream;
dm.qryBlb.Next;
end;
FillMessage(Msg);
finally
Free;
end;
for s in FMailPac.Split([',']) do
begin
EmailAddress := Trim(s);
if EmailAddress <> '' then
begin
with Msg.recipients.Add do
begin
Address := EmailAddress;
end;
end;
end;
for s in MailCC.Split([',']) do
begin
EmailAddress := Trim(s);
if EmailAddress <> '' then
Msg.CCList.Add.Address := EmailAddress;
end;
for s in MailCCO.Split([',']) do
begin
EmailAddress := Trim(s);
if EmailAddress <> '' then
Msg.BccList.Add.Address := EmailAddress;
end;
finally
SMTP1.Send(Msg);
end;
TIdMessageBuilderHtml supports adding attachments via streams, as well as via files. However, those streams have to remain alive for the duration that the TIdCustomMessageBuilder.Attachments collection is populated, which is not an option in your case since you are looping through DB records one at a time, thus you would only be able to access 1 DB stream at a time.
You could create a local array/list of TMemoryStream objects, and then populate the TMessageBuilderHtml with those streams, but you will end up wasting a lot of memory that way since TIdMessageBuilderHtml would make its own copy of the TMemoryStream data. And there is no way to have TIdMessageBuilderHtml just use your TMemoryStream data as-is in a read-only mode (hmm, I wonder if I should add that feature!).
The reason why your manual TIdAttachmentMemory objects don't work is simply because TIdCustomMessageBuilder.FillMessage() clears the TIdMessage's body before then re-populating it, thus losing your attachments (and various other properties that you are setting manually beforehand).
You would have to add your DB attachments to the TIdMessage after FillMessage() has done its work first. But, then you risk TIdMessageBuilderHtml not setting up the TIdMessage structure properly since it wouldn't know your DB attachments exist.
On a side note, you are not using TIdAttachmentMemory correctly anyway. Do not call its CloseLoadStream() method if you have not called its OpenLoadStream() method first. Calling its LoadFromStream() method is enough in this case (or, you can even pass the TStream to TIdAttachmentMemory's constructor). Do note, however, that you are leaking the TStream returned by dm.GetDataBlbStrm().
So, in this case, you are probably better off simply populating the TIdMessage manually and not use TIdMessageBuilderHtml at all. Or, you could derive a new class from TIdMessageBuilderHtml (or TIdCustomMessageBuilder directly) and override its virtual FillBody() and FillHeaders() methods to take your DB streams into account.

Error on Indy HTTP post using TStringList as sending parameter

I have created a little program which access a webservice of mail service. The same code works on Delphi 7 with indy 9, but doesn't works with Delphi Seattle with Indy 10.
My Stringlist is built that way:
ParametrosConsulta.Values['nCdEmpresa'] := Edt_Cod.Text;
ParametrosConsulta.Values['&sDsSenha'] := Edt_Sen.Text;
...
My post have a sending parameter a stringlist, which has a text like that:
nCdEmpresa= &sDsSenha= &nCdServico=41106&sCepOrigem=88905355&sCepDestino=88906768&nVlPeso=20.0&nCdFormato=1&nVlComprimento=20.0&nVlAltura=20.0&nVlLargura=20.0&nVlDiametro=6.0&sCdMaoPropria=N&nVlValorDeclarado=0&sCdAvisoRecebimento=N
then i call idHttp.Post like that, which ParametroConsulta holds the text i've shown before, and Resposta is a TStringStream which holds the response of the request:
IdHttp.Request.Clear;
IdHttp.Request.Host := 'ws.correios.com.br';
IdHttp.Request.ContentType := 'application/x-www-form-urlencoded';
idHTTP.Request.UserAgent := 'Mozilla/3.0 (compatible;Indy Library)';
IdHTTP.Request.Charset := 'utf-8';
IdHTTP.ProtocolVersion := pv1_1;
{...}
try
Application.ProcessMessages;
FrmPrincipal.Refresh;
IdHttp.Post('http://ws.correios.com.br/calculador/CalcPrecoPrazo.asmx/CalcPrecoPrazo', ParametrosConsulta, Resposta);
except
on E:EIdHttpProtocolException do
begin
ShowMessage(E.Message + ' - ' + E.ErrorMessage);
PnlEnviando.Visible := False;
Exit;
end;
end;
But after post, the webservice returns that sDsSenha is missing (this parameter can hold a empty space).
Don't write &:
ParametrosConsulta.Values['sDsSenha'] := Edt_Sen.Text;
Ampersand are adding by Indy automatically. Btw, you may need to use TIdURI.PathEncode(Edt_Sen.Text) for escaping some characters.

SMTP.host variable issue

Here is my Issue. I'm using Delphi 5 and Indy 9. I have no option to upgrade either at the moment. I am trying to send an email via gmail, and i hard code the string as 'smtp.google.com' it works just fine. however, if smtp.host is getting the host name from a variable it fails with error 11001, and i havent been able to figure out why. I'm new using indy so i'm probably missing something silly, but i don't understand why it could accept a string as the host, but not a variable holding the string. (It's got to be a variable because i need to pass the procedure different SMTP hosts based on the user signed in. Here is the code:
procedure TFormEmail.SendSimpleMail(ToEmail, MyAttachment: string);
var
Msg: TIdMessage;
DestAddr: TIdEmailAddressItem;
SMTP: TIdSMTP;
SSLHandler : TidSSLIOHandlerSocket;
Attachment: TIdAttachment;
SMTPHost1 :string;
begin
Msg := idMessage1;
Msg.From.Text := EmailName(My_User);
Msg.From.Address := EmailAddress(My_User);
msg.Subject := 'Test';//email subject
DestAddr := Msg.Recipients.Add;
DestAddr.Text := '';//receiver's name (optional)
DestAddr.Address := ToEmail;//where its going
Msg.Body.Add(edtEmailBody.text); //email body
SMTP := IdSMTP1;
SMTP.IOHandler := idSSLIOHandlerSocket1;
SMTPhost1 := SMTPHost(My_User);
SMTPhost1 := 'smtp.google.com';
//SMTP.Host := SMTPhost1; //<--FAILS
SMTP.Host := 'smtp.google.com'; //<--SUCCEEDS
SMTP.Port := SMTPPort(My_User);
SMTP.AuthenticationType := atLogin; //error here (2 error)
SMTP.Username := EmailAddress(My_User);
SMTP.Password := SMTPPassword(My_User);
If not empty(MyAttachment) then
Attachment := TIdAttachment.Create(Msg.MessageParts, MyAttachment);//loads Att
Try
SMTP.Connect;
except
SMTP.Connect;//refire if exception (issue with INDY)
end;
if useSSL(My_User) then
SMTP.SendCmd('STARTTLS');//load TLS
SMTP.Authenticate;
SMTP.Send(Msg);
SMTP.Disconnect;//disconnect from server
end;
I marked the one that fails and the one that succeeds, but i don't understand what i'm doing wrong. Any help would be appreciated
Seems you have more issues in one question, I can only help you with one.
I had the same issue with Connect, I simply called the Load method from IdSSLOpenSSLHeaders.
Try the following:
SMTP := IdSMTP1;
IdSSLOpenSSLHeaders.Load;
SMTP.IOHandler := idSSLIOHandlerSocket1;

How to get body texts of all e-mail messages from a certain IMAP mailbox?

How to get body texts of all e-mail messages from a certain IMAP mailbox in Delphi ? For example, from the INBOX mailbox ?
There are many ways to retrieve all body texts of all messages from the selected mailbox. I've used the one, where you iterate the mailbox and Retrieve every single message from the mailbox one by one. This way allows you to modify the code, so that you'll be able to break the loop when you need or e.g. replace Retrieve by RetrievePeek which won't mark the message as read on server like the first mentioned does. When the message is retrieved from server, all its parts are iterated and when it's the text part, its body is appended to a local S variable. After the iteration the S variable is added to the output BodyTexts string list. So, as the result you'll get string list collection where each item consists from the concatenated message's text part bodies and where each item means one message.
uses
IdIMAP4, IdSSLOpenSSL, IdText, IdMessage, IdExplicitTLSClientServerBase;
procedure GetGmailBodyTextParts(const UserName, Password: string;
BodyTexts: TStrings);
var
S: string;
MsgIndex: Integer;
MsgObject: TIdMessage;
PartIndex: Integer;
IMAPClient: TIdIMAP4;
OpenSSLHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
BodyTexts.Clear;
IMAPClient := TIdIMAP4.Create(nil);
try
OpenSSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
try
OpenSSLHandler.SSLOptions.Method := sslvSSLv3;
IMAPClient.IOHandler := OpenSSLHandler;
IMAPClient.Host := 'imap.gmail.com';
IMAPClient.Port := 993;
IMAPClient.UseTLS := utUseImplicitTLS;
IMAPClient.Username := UserName;
IMAPClient.Password := Password;
IMAPClient.Connect;
try
if IMAPClient.SelectMailBox('INBOX') then
begin
BodyTexts.BeginUpdate;
try
for MsgIndex := 1 to IMAPClient.MailBox.TotalMsgs do
begin
MsgObject := TIdMessage.Create(nil);
try
S := '';
IMAPClient.Retrieve(MsgIndex, MsgObject);
MsgObject.MessageParts.CountParts;
if MsgObject.MessageParts.TextPartCount > 0 then
begin
for PartIndex := 0 to MsgObject.MessageParts.Count - 1 do
if MsgObject.MessageParts[PartIndex] is TIdText then
S := S + TIdText(MsgObject.MessageParts[PartIndex]).Body.Text;
BodyTexts.Add(S);
end
else
BodyTexts.Add(MsgObject.Body.Text);
finally
MsgObject.Free;
end;
end;
finally
BodyTexts.EndUpdate;
end;
end;
finally
IMAPClient.Disconnect;
end;
finally
OpenSSLHandler.Free;
end;
finally
IMAPClient.Free;
end;
end;
This code requires OpenSSL, so don't forget to put the libeay32.dll and ssleay32.dll libraries to a path visible to your project; you can download OpenSSL libraries for Indy in different versions and platforms from here.

How to show on front (and not on background) a new email form in Outlook with OLE?

I am using OLE with Delphi to communicate from my delphi app to Outlook.
I am opening the new email form in Outlook using the following code. The problem is that the form is on background, so if the form from which I am generating the email form is maximized it will "cover" the Outlook new mail form.
How can I make that form appear it on top? (not "sticking on top", but simply that it appears on top, then a user can mimimize it if they want).
This is the code:
try
OutlookApp := GetActiveOleObject('Outlook.Application');
except
OutlookApp := CreateOleObject('Outlook.Application');
end;
try
MailItem := OutlookApp.CreateItem(olMailItem);
MailItem.To := 'Test#mail.com';
MailItem.Subject := 'This is the subject';
MailItem.HTMLBody := '<HTML>Test</HTML>';
MailItem.Display;
finally
OutlookApp := VarNull;
end;
end;
Just add one more call:
MailItem.Display;
OutlookApp.ActiveWindow.Activate;
Activate brings Outlook to front.
The MailItem.Display has the optional parameter Modal which should make your window modal, so try to use:
MailItem.Display(True);
I realize that this is an old topic, but I had the same problem recently for users with Outlook 2016. For me, the solution needed to be able to include a signature and an attachment and open the new Outlook window on top. If you call MailItem.Display(True), you lose the attachment. This was the solution that worked for me.
Notice the extra space after "Message (HTML)" in the Window name. It took me a long time to discover that the window name for new HTML emails has an extra blank space at the end.
procedure DisplayMail(Address, Subject, Body: string; Attachment: TFileName);
var
Outlook: OleVariant;
Mail: OleVariant;
H : THandle;
TempTitle : String;
begin
TempTitle := 'Temp-'+IntToStr(Random(1000000));
try
Outlook := GetActiveOleObject('Outlook.Application');
except
Outlook := CreateOleObject('Outlook.Application');
end;
Mail := Outlook.CreateItem(olMailItem);
Mail.To := Address;
Mail.Subject := TempTitle;
Mail.Display(false);
H := FindWindow('rctrl_renwnd32',PWidechar(TempTitle+' - Message (HTML) '));
Mail.Subject := Subject;
if Body <> '' then
Mail.HTMLBody := InsertMessage(Body,Mail.HTMLBody);
if Attachment <> '' then
Mail.Attachments.Add(Attachment);
try
if H <> 0 then
SetForegroundWindow(H);
finally
end;
end;
This worked for me, and it allows me to add an attachment, and it retains the default signature. I hope this helps somebody.

Resources