9 de maio de 2011

Enviando emails com Delphi - Parte II

Há alguns dias, escrevi um post sobre o envio de emails com Delphi, mostrando a configuração básica dos componentes Indy para SMTP e também as funções que devem ser chamadas para completar o processo. No exemplo, a mensagem incluída no email era um texto simples, sem qualquer tipo de formatação.

No começo, os programas leitores de email só conseguiam trabalhar com mensagens assim, onde apenas texto era trocado. É um comportamento básico mas que ainda tem sua utilidade, principalmente quando há questões de segurança envolvidas. Com a evolução da internet, entretanto, praticamente todos os leitores de email conseguem exibir mensagens com formatação rica. Usando HTML podemos compor emails com layouts complexos, usando cores e tipos de fontes diferentes, além da inclusão de imagens, links, tabelas, animações, entre outros. Uma grande vantagem desse formato é que ele já é utilizado para construir os sites da internet. Ou seja, HMTL é uma ferramenta bastante conhecida e amplamente suportada.

Do ponto de vista do envio de email, o que há de diferente em relação ao que foi apresentado no outro post ? Basicamente, apenas dois detalhes. O primeiro é uma propriedade da mensagem que determina o tipo de conteúdo que a mensagem está trafegando. Essa característica é chamada MIME (Multipurpose Internet Mail Extensions, ou Extensões Multipropósito para Correio na Internet). O padrão para essa propriedade no corpo de um email é o valor text/plain, que significa que a mensagem é composta por um texto simples. Como queremos montá-la como um HTML, temos que avisá-la disso:
IdMessage1.ContentType := 'text/html';
IdMessage1.CharSet := 'ISO-8859-1';

Veja que eu também modifiquei o charset da mensagem. Esta propriedade indica qual é o conjunto de caracteres que a mensagem está usando. Como o português usa acentuação, é preferível trocar o padrão americano pelo conjunto latino ('ISO-8859-1').

O outro detalhe é que o corpo da mensagem agora tem que respeitar o MIME configurado. Quero dizer com isso que teremos que montar um HTML para a mensagem:
IdMessage1.Body.Text := '<html><head>' +
'<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">' +
'</head><body>' +
'<p>Essa mensagem é um <b>Teste</b> de email HTML.</p>' +
'</body></html>';

Embora pareça redundante, é importante informar de novo no cabeçalho do HTML quem é o MIME e o charset. Os leitores de email utilizam as informações desse cabeçalho para renderizar a mensagem recebida via SMTP.

E se quisermos incluir uma imagem em nosso HTML ? Algumas pessoas publicam a imagem na internet e referenciam o link dela no HTML da mensagem. Mas essa solução exige que o destinatário esteja conectado para poder buscar a imagem, coisa que nem sempre é verdade. Numa empresa, por exemplo, as mensagens são recebidas de forma centralizada por um servidor de email, dispensando a necessidade de ter internet local. Muitas delas adotam ainda restrições ao acesso à internet.

Uma maneira melhor de se fazer isso é enviar a imagem junto com o HTML. Ao invés de preencher o campo Body da mensagem, teremos que decompô-la em seus constituintes básicos para podermos identificar de forma separada o MIME de cada um. Isto é, o corpo da mensagem é um HTML enquanto a imagem é um arquivo binário com MIME próprio. O conjunto todo terá um MIME especial (multipart/mixed) que sinaliza que a mensagem está montada como múltiplas partes independentes.

Cada parte deverá ser anexada à mensagem através da propriedade MessageParts. Para o corpo, usamos o tipo TIdText. Para a imagem, o tipo tradicional para anexos (TIdAttachmentFile).
var
text : TIdText;
at : TIdAttachmentFile;
begin
IdMessage1.MessageParts.Clear();
IdMessage1.ContentType := 'multipart/mixed';

text := TIdText.Create (IdMessage1->MessageParts, Nil);
text.ContentType := 'text/html';
text.CharSet := 'ISO-8859-1';
text.Body.Text := PreparaHTML();

at := TIdAttachmentFile.Create (IdMessage1->MessageParts,
'C:\\Inetpub\\wwwroot\\winxp.gif');
at.ContentType := 'image/gif';
at.FileName := 'winxp.gif';

IdSMTP1.Connect();
IdSMTP1.Send(IdMessage1);
{ ... }

O HTML pode, então, fazer referência diretamente ao nome do arquivo anexado, sem precisar estipular um endereço na internet. A única coisa é que o nome do arquivo deve ser exatamente igual àquele informado na propriedade FileName do anexo:
function TForm1.PreparaHTML : String;
begin
{ ... }
Result := Result + '<img style="margin:1px 1px 1px 0px;" src="winxp.gif" />';
{ ... }
end;

Em tese, não há restrição a quantos recursos externos podem ser agregados ao email usando esse tipo de construção. Também não se restringe à inclusão de imagens, podendo ser usado para incorporar animações, scripts, folhas de estilo, etc. - desde que tenham os respectivos MIMEs devidamente configurados.

Estou com a versão 10 do Indy que vem com o Delphi XE mas acredito que os recursos mostrados aqui estejam disponíveis em versões anteriores também.

Mais Informações
Enviando emails com Delphi

21 comentários :

vicente neto disse...

TIdText não é reconhecido no delphi 2010, o que faço para substituir ?

fabio lima disse...

Sou especialista em SI pela UFLA gostaria de fazer uma indicação para sua equipe, tenho um colega na petrobrás que é um gênio em ti, seria interessante aproveitar o conhecimento de todos para melhorar a tecnologia em ti do nosso país.
um abraço.

Luís Gustavo Fabbro disse...

Fábio

O que exatamente quer dizer com "indicação pra minha equipe" ? Na verdade, o blog hj é apoiado pela ABC71 mas mantido apenas por mim...

Se a ideia é submeter posts ao blog, toda ajuda é bem-vinda ! Pode remeter o texto ao email do blog e, se for o caso, o publicarei com os devidos créditos.

[]s

Luís Gustavo Fabbro disse...

Vicente

O TidText mudou de lugar. No Indy9, ele estava junto com a unit IdMessage enquanto no Indy10 ele foi deslocado para uma unit própria.

Isto quer dizer que basta adicionar a unit IdText na cláusula uses do seu fonte para que ele encontre o TIdText.

[]

João Enoc disse...

Saudações Luís Gustavo.

Parabéns pelos post's.

Estou com um probleminha no envio de e-mail.

Desenvolvi um software em Delphi 2010 com MySql. Nesse software, o cliente deve efetuar um login.

Acontece que estou tentando implementar uma opção para o cliente de reenvio de senha, caso esse não a lembre.

Fiz alguns testes com a dupla IdSMTP e TIdMessage. O procedimento é bem simples, pois o servidor tem a opção sem o protocolo SSL. Entretanto quando envio um e-mail, esse e-mail não chega no destino. Também não ocorre nenhuma mensagem de erro ao enviá-lo.

A porta sugerida pelo site de hospedagem, sem o protocolo SSL, é a 26.

Através do Outlook 2007 consigo enviar os e-mails normalmente.

Se você puder me ajudar com alguma ideia, agradeço.

Luís Gustavo Fabbro disse...

João

Você pode criar um log inteceptando a comunicação do idSMTP com o servidor pra ver onde está falhando. Inclua um TIdLogDebug ou um TIdLogFile (ambos estão na guia Indy Intercepts)em seu projeto, vinculando-o à propriedade Intercept do idSMTP.

[]s

João Enoc disse...

Caro Luís Gustavo.

Obrigado pela dica.

Gerei o log, como você sugeriu.

Parece que tudo correu bem. Mas, de qualquer forma vou postar a mensagem.

Por questões éticas não coloquei o nome do provedor e dos e-mail's. o que estiver em parêntesis foi alterado.


Stat Connected.
Recv 10/02/2012 22:45:28: 220-br54.(PROVEDOR) ESMTP Exim 4.69 #1 Fri, 10 Feb 2012 22:45:31 -0200 220-We do not authorize the use of this system to transport unsolicited, 220 and/or bulk e-mail.
Sent 10/02/2012 22:45:28: EHLO TeCMaisComptud
Recv 10/02/2012 22:45:28: 250-br54.(PROVEDOR) Hello TeCMaisComptud [(IP)]250-SIZE 52428800250-PIPELINING250-AUTH PLAIN LOGIN250-STARTTLS250 HELP
Sent 10/02/2012 22:45:28: AUTH LOGIN
Recv 10/02/2012 22:45:28: 334 VXNlcm5hbWU6
Sent 10/02/2012 22:45:28: c2VkaW1AZGF0YWxvZ3VlLmNvbS5icg==
Recv 10/02/2012 22:45:28: 334 UGFzc3dvcmQ6
Sent 10/02/2012 22:45:28: NjU3MjMyNDM5ODk=
Recv 10/02/2012 22:45:29: 235 Authentication succeeded
Sent 10/02/2012 22:45:29: RSET
Recv 10/02/2012 22:45:29: 250 Reset OK
Sent 10/02/2012 22:45:29: MAIL FROM: <(E-MAIL)>
Recv 10/02/2012 22:45:29: 250 OK
Sent 10/02/2012 22:45:29: RCPT TO:<(E-MAIL)>
Recv 10/02/2012 22:45:29: 250 Accepted
Sent 10/02/2012 22:45:29: DATA
Recv 10/02/2012 22:45:29: 354 Enter message, ending with "." on a line by itself
Sent 10/02/2012 22:45:29: From: "(E-MAIL)" <(E-MAIL)>Subject: TesteTo: (E-MAIL)MIME-Version: 1.0Date: Fri, 10 Feb 2012 22:45:29 -0200
Sent 10/02/2012 22:45:29: Content-Type: text/html
Sent 10/02/2012 22:45:29:
Sent 10/02/2012 22:45:29: Content-Transfer-Encoding: quoted-printable
Sent 10/02/2012 22:45:29: Content-Disposition: inline
Sent 10/02/2012 22:45:29:
Sent 10/02/2012 22:45:29:
Sent 10/02/2012 22:45:29: teste
Sent 10/02/2012 22:45:29:
Sent 10/02/2012 22:45:29: .
Recv 10/02/2012 22:45:30: 250 OK id=1Rw15p-0006j5-Hd
Sent 10/02/2012 22:45:30: QUIT
Recv 10/02/2012 22:45:30: 221 br54.(PROVEDOR) closing connection
Stat Disconnected.

Luís Gustavo Fabbro disse...

João

Aparentemente o envio foi mesmo feito corretamente. Não conheço muito do gerenciamento do servidor de email mas creio que seja possível incluir nele regras que podem estar redirecionando ou barrando seu email.

Verifique se não há regras estipulando, por exemplo, que apenas emails de remementes confiáveis são entregues. Por causa disso, suas mensagens podem ter sido incluídas na caixa de spam.

[]s

Eduardo Flaeschen disse...

Olá Luís Gustavo, estava já há algum tempo procurando uma solução para montar e-mails com imagens e agora encontrei a solução com as suas dicas. Obrigado e parabéns !!!
Eduardo Flaeschen

Eduardo Flaeschen disse...

Olá Luís Gustavo, estava procurando já há algum tempo uma solução para o envio de e-mails com imagens e encontrei nessa sua publicação, muito simples e eficiente. Parabéns e obrigado.

Anônimo disse...

Olá Luís!

Aprecio muito os seus posts. Eles são bem escritos, fluentes e bem claros.

Há algum tempo desenvolvi uma aplicação que, entre outras coisas, envia um e-mail automático para alguns usuários. Para ter controle se o e-mail foi recebido, incluí o linha:
IdMessage1.ReceiptRecipient.Text:=From; // onde: From é o endereço do destinatário.

O problema é que nunca recebi a confirmação. Executo semanalmente essa aplicação e o e-mail de confirmação nunca chega e acabo tendo que ligar para o usuário para tirar a dúvida do recebimento.

O que devo verificar para corrigir esse funcionamento inesperado?

Um grande abraço e sucesso!

Vandinei Santos

Luís Gustavo Fabbro disse...

Vandinei

A solicitação de confirmação de leitura é apenas uma solicitação, isto é, não há obrigatoriedade do leitor de email honrá-la. No Outlook, por exemplo, há uma configuração onde você pode optar por nunca enviar a notificação, sempre enviar ou perguntar caso a caso se o usuário deseja honrar.

Veja se não é esse o caso do seu cliente.

[]s

Anônimo disse...

Luís bom dia,

Obrigado pelas dicas, muito boas.

Estou com um problema e to quebrando a cabeça pra resolver.

O sistema envia o e-mail, porem eu recebo no o código fonte do e-mail. com todos cabeçalhos, com o texto plano e o html inteiro.

Além da imagem que fica só uma sequencia de caracteres.

Tem alguma idéia do que pode ser?

Atenciosamente.
João Mattos

Luís Gustavo Fabbro disse...

João

Dependendo do client que vc está usando para ler o email, pode ser necessário adicionar outras informações de cabeçalho da mensagem.

Reveja tb os parâmetros de contenttype e charset tanto da mensagem como um todo quanto em cada uma das partes que compõem a sua mensagem.

[]s

Perfil Sistemas disse...

Minha mensagens não saem com acentuação de jeito nenhum.
Mesmo usando IdMessage1.CharSet := 'ISO-8859-1';
Insisti em sair sem acentuação

Luís Gustavo Fabbro disse...

Como vc montou a mensagem? Texto, HTML, multipart?

Unknown disse...

Caro Luís,
Adorei o post, muito bem escrito. No meu caso, estou tentando enviar mensagens 'text/plain' com anexos, mas os arquivos anexados insistem em ir para o corpo do texto, em caracteres ilegíveis. Alguma sugestão? Obrigado,
Ricardo

Luís Gustavo Fabbro disse...

Como você montou o código para envio da mensagem? Para este tipo de conteúdo, você pode usar direto o body no idmensagem, sem criar partes separadas para ele. Acredito tb que o ContentType da mensagem deve ser mudado para plain/text.

[]s

Unknown disse...

Uso a função abaixo (já tentei diversas variações), mas só consigo anexar corretamente quando não trato o charset, mas nesse caso os caracteres acentuados não aparecem corretamente.

function EnviaEmail(Host, UserName, Password, FromAddress, FromName,
ToAddresses, Subject, Body: string; Attachments: string = ''): boolean;
var
smtp: TIdSMTP;
email: TIdMessage;
aux: TStringList;
i: Integer;
anexo: TIdAttachment;
//texto: TIdText;
begin
smtp := TIdSMTP.Create(nil);
email := TIdMessage.Create(nil);
try
try
smtp.Host := Host;
smtp.Username := UserName;
smtp.Password := Password;
email.From.Address := FromAddress;

email.ContentType := 'text/plain'; //sem estas linhas os anexos
email.CharSet := 'ISO-8859-1'; //funcionam corretamente, mas
//não a acentuação
email.IsEncoded := True;
email.Encoding := mePlainText;
email.ContentTransferEncoding := '8bit';

if FromName = '' then
FromName := FromAddress;
email.From.Name := FromName;
email.Recipients.EMailAddresses := ToAddresses;
email.Subject := Subject;

//texto := TIdText.Create(email.MessageParts); //não funcionou
//texto.Body.Text := Body;
//texto.ContentType := 'text/plain';

email.Body.Text := Body;

if Attachments <> '' then
begin
aux := TStringList.Create;
aux.DefaultEncoding := TEncoding.ANSI;
try
aux.Delimiter := ';';
aux.StrictDelimiter := True;
aux.DelimitedText := Attachments;
for i := 0 to aux.Count - 1 do
begin
anexo := TIdAttachmentFile.Create(email.MessageParts, aux[i]);
//anexo.ContentType := 'binary';
anexo.ContentDisposition := 'attachment';
end;
finally
aux.Free;
end;
end;
smtp.Connect;
try
smtp.Send(email);
finally
smtp.Disconnect;
end;
result := True;
finally
email.Free;
smtp.Free;
end;
except
result := False;
end;
Application.ProcessMessages;
sleep(3000);
end;

Unknown disse...

Estou usando esta função, mas ainda sem sucesso:

function EnviaEmail(Host, UserName, Password, FromAddress, FromName,
ToAddresses, Subject, Body: string; Attachments: string = ''): boolean;
var
smtp: TIdSMTP;
email: TIdMessage;
aux: TStringList;
i: Integer;
anexo: TIdAttachment;
//texto: TIdText;
begin
smtp := TIdSMTP.Create(nil);
email := TIdMessage.Create(nil);
try
try
smtp.Host := Host;
smtp.Username := UserName;
smtp.Password := Password;
email.From.Address := FromAddress;

email.ContentType := 'text/plain'; //sem estas linhas os anexos
email.CharSet := 'ISO-8859-1'; //funcionam corretamente, mas
//não a acentuação
email.IsEncoded := True;
email.Encoding := mePlainText;
email.ContentTransferEncoding := '8bit';

if FromName = '' then
FromName := FromAddress;
email.From.Name := FromName;
email.Recipients.EMailAddresses := ToAddresses;
email.Subject := Subject;

//texto := TIdText.Create(email.MessageParts); //não funcionou
//texto.Body.Text := Body;
//texto.ContentType := 'text/plain';

email.Body.Text := Body;

if Attachments <> '' then
begin
aux := TStringList.Create;
aux.DefaultEncoding := TEncoding.ANSI;
try
aux.Delimiter := ';';
aux.StrictDelimiter := True;
aux.DelimitedText := Attachments;
for i := 0 to aux.Count - 1 do
begin
anexo := TIdAttachmentFile.Create(email.MessageParts, aux[i]);
//anexo.ContentType := 'binary';
anexo.ContentDisposition := 'attachment';
end;
finally
aux.Free;
end;
end;
smtp.Connect;
try
smtp.Send(email);
finally
smtp.Disconnect;
end;
result := True;
finally
email.Free;
smtp.Free;
end;
except
result := False;
end;
Application.ProcessMessages;
sleep(3000);
end;

Luís Gustavo Fabbro disse...

Ricardo

Colei seu código num fonte meu e consegui enviar o email plain/text com anexo usando o body da mensagem setando o CharSet para UTF-8 e sem especificar o ContentType.

O anexo apareceu conforme esperado e a acentuação também ficou ok.

PS: sua configuração do SMTP não tem a porta; é interessante prever a passando dessa informação para a função que envia o email.

[]s

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.