14 de abril de 2011

Enviando emails com Delphi - Parte I

O email é há bastante tempo uma ferramenta corriqueira, das mais usadas nesses tempos de internet. Mas, a utilidade dele não está restrita à troca de informações entre pessoas - seja por razões pessoais ou profissionais. Um sistema computacional também pode tirar proveito dessa ferramenta ao permitir, por exemplo, notificar usuários a respeito de alguma ocorrência no escopo do sistema.

Num sistema, são diversas as situações onde o envio de um email é aplicável: uma situação de erro encontrada, algum problema potencial que exija intervenção do usuário, alguma ação dentro de um workflow (por exemplo, uma aprovação que necessite do aval do usuário), troca de correspondência automática com Clientes e Fornecedores (envio de um pedido ou de uma cotação, por exemplo), remeter informações em forma de relatórios para lastrear tomadas de decisões, etc.

A forma mais utilizada para enviar de emails é através do protocolo SMTP - Simple Mail Transfer Protocol. Este é um protocolo simples, constituído pela troca de mensagens em formato texto enviadas numa ordem precisa através de TCP/IP. Mesmo sendo um protocolo relativamente simples, é mais fácil usar classes prontas que o implementem, como as publicadas pelo Projeto Indy e que são distribuídas junto com o IDE do Delphi e do C++ Builder.

Um exemplo básico usando as classes do Indy pode ser visto no quadro abaixo. Ele mostra o uso de algumas das principais propriedades necessárias para concretizar um envio:
var IdSMTP : TIdSMTP;
IdMessage : TIdMessage;
begin
IdSMTP := TIdSMTP.Create (Nil);
IdMessage := TIdMessage.Create (Nil);

{ Informações básicas para uma conexão }
IdSMTP.Host := edServidor.Text;
IdSMTP.Port := edPorta.Text;

{ Tenta conectar no servidor, aguardando no máximo um minuto }
IdSMTP.Connect (60000);

{ Nome e endereço de quem está enviando }
IdMessage.From.Address := edEnderecoDe.Text;
IdMessage.From.Name := edNomeDe.Text;

{ Destinatários }
IdMessage.Recipients.EMailAddresses := edEnderecoPara.Text;

{ Destinatários em cópia }
IdMessage.CCList.EMailAddresses := edCcPara.Text;

{ Destinatários em cópia oculta }
IdMessage.BccList.EMailAddresses := edBCcPara.Text;

{ Endereço para resposta, isto é, se um destinatário der um "reply", a resposta será enviada para este endereço }
IdMessage.ReplyTo.EMailAddresses := edResponderPara.Text;

{ Assunto da mensagem }
IdMessage.Subject := edAssunto.Text;

{ Corpo da mensagem, isto é, o texto dela }
IdMessage.Body.Text = edMensagem.Text;

{ Envia o e-mail }
IdSMTP.Send (IdMessage);

{ Desconecta }
IdSMTP.Disconnect ();

FreeAndNil (IdMessage);
FreeAndNil (IdSMTP);
end;

Esse é o exemplo mais básico possível, acessando um servidor de email que não exige autenticação nem conexão segura. A mensagem enviada também é simples - apenas texto comum, sem anexos. Ele demonstra a interação entre as classes TIdSMTP (que implementa o protocolo de comunicação em si) e TIdMessage (que contem a mensagem que será enviada). Primeiro, uso o TIdSMTP para estabelecer uma conexão com o servidor; uma vez conectado, ele pode enviar a mensagem previamente preparada; finalmente, depois que o email foi enviado, pode-se solicitar a desconexão ao servidor.

Dificilmente um servidor de email hoje não exigirá autenticação do usuário. Mas, essa configuração é trivial, bastando passar o nome do usuário e sua senha antes de tentar se conectar :
if (UsaAutenticacao) then
begin
IdSMTP.AuthenticationType := atLogin;
IdSMTP.Username := edUsuario.Text;
IdSMTP.Password := edSenha.Text;
end;

Ajustar o valor da propriedade AuthenticationType também é necessário pois é ela que instrui o componente a submeter a autenticação.

Já adicionar segurança na comunicação através de SSL ou TLS demandará um pouco mais de trabalho. Isso envolve a criação de instância de uma nova classe - TIdSSLIOHandlerSocket (na versão 9 do Indy) ou TIdSSLIOHandlerSocketOpenSSL (na versão 10). O código abaixo mostra um exemplo:
_SslHandler := TIdSSLIOHandlerSocketOpenSSL.Create(Nil);
_SslHandler.SSLOptions.Method := sslvSSLv23;
_SslHandler.SSLOptions.Mode := sslmClient;

{ Associa ao SMTP a configuração para segurança }
IdSMTP.IOHandler := _SslHandler;

O código até que é relativamente simples. A instância da classe é criada e são ajustadas algumas propriedades, como a versão do protocolo (método de autenticação) que será usada. Caso seja necessário associar um certificado digital ao envio, as opções do SSL permitem fazer as configurações pertinentes. Antes do SMTP se conectar, o novo manipulador para comunicação segura é atribuído à instância da classe de envio.

O problema é que o Indy não implementa SSL nem TLS. As classes disponibilizadas apenas mapeiam o funcionamento existente nas bibliotecas mantidas pelo Projeto OpenSSL, um projeto open source dedicado exclusivamente à segurança de comunicação baseada em criptografia e nos protocolos SSL/TLS. Há diversas versões das bibliotecas do OpenSSL espalhadas pela internet, compatíveis com inúmeras versões de Delphi/C++ Builder e diferentes sistemas operacionais. Em último caso, o site do projeto disponibiliza os fontes para quem preferir gerar sua própria versão.

Anexar arquivos à mensagem também requer uma nova classe mas o uso dela é mais simples. Para cada arquivo que desejar anexar, instancie um TIdAttachment vinculado à mensagem antes de enviá-la:
TIdAttachment.Create (
IdMessage.MessageParts,
'c:\temp\arq.doc');

O primeiro parâmetro passado ao construtor é uma estrutura da mensagem onde são armazenadas partes externas que a constituem (como os anexos). O outro parâmetro é o nome de um arquivo mas, na versão 10 do Indy, há mecanismos para você anexar diretamente um stream qualquer, isto é, não é preciso que seja um arquivo.

Pode parecer estranho chamar o construtor e não atribuir o resultado a variável alguma. Acontece que, ao vincular o anexo ao MessageParts, este passa a ser responsável por devolver os recursos usados (memória). Então, não é preciso manter uma variável com a instância nem aplicar-lhe o destrutor quando não for mais usada já que isso será feito automaticamente.

Em outros posts, mostro como enviar email no formato HTML, inclusive usando o componente PageProducer para gerar conteúdo. Também mostrarei como incluir imagens embutidas no HTML a ser enviado.

56 comentários :

Anônimo disse...

Bom Dia Luis Gustavo tudo bem??

Gostaria de saber qual é o problema que está causando no meu código. Eu já pesquisei em vários lugares e eles falam que se mudar o componente para o Indy 9 resolveria, porem eu continuo com o mesmo problema.

Eu uso a versão Indy 9.00.10

O erro é o seguinte: Could not load SSL libary. No código abaixo eu aponto o erro.

var
Mensagem: TIdMessage;
Conexao: TIdSMTP;
SSLSocket: TIdSSLIOHandlerSocket;
begin
Mensagem := TIdMessage.Create(nil);
Conexao := TIdSMTP.Create(nil);
SSLSocket := TIdSSLIOHandlerSocket.Creat(nil);

Mensagem.From.Address := EnviaEmail.Remetente;
Mensagem.From.Name := EnviaEmail.NomeRemetente;

Conexao.Port := 25; //EnviaEmail.Porta;
Conexao.AuthenticationType := atLogin;
Conexao.Host := EnviaEmail.Host;
Conexao.Username := EnviaEmail.Remetente;
Conexao.Password := EnviaEmail.SenhaEmail;
Conexao.ReadTimeout := 0;

SSLSocket.SSLOptions.Method := sslvSSLv23;
SSLSocket.SSLOptions.Mode := sslmClient;
SSLSocket.SSLOptions.VerifyMode := [];
SSLSocket.SSLOptions.VerifyDepth := 0;
Conexao.IOHandler := SSLSocket;

Mensagem.Recipients.EMailAddresses := EnviaEmail.Destinatario;
Mensagem.CCList.EMailAddresses := EnviaEmail.CopiaPara;
Mensagem.Priority := mpNormal;
Mensagem.Subject := EnviaEmail.Assunto;
Mensagem.Body.Clear;

with TIdText.Create(Mensagem.MessageParts) do
begin
Body.Text := 'This is a plain text message';
ContentType := 'text/plain';
end;

with TIdText.Create(Mensagem.MessageParts) do
begin
Body.Text := EnviaEmail.Mensagem;
ContentType := 'text/html';
end;

TIdAttachment.Create(Mensagem.MessageParts, TFileName(sCaminhoNFe));
Conexao.Connect; --> Aqui ocorre o erro

try
Conexao.Send(Mensagem);
Result := True;
except
on E:Exception do
begin
SetMsgErroEnvio(E.Message);
Conexao.Disconnect;
Result := False;
end;
end;
end;

O que pode ser??

Uma outra obs. Eu uso a dll
libeay32.dll Versão 1.0.0.4
ssleay32.dll Versão 1.0.0.4

Desde já agradeço sua atenção.

Abraços

Felipe Luiz Carnevali

Luís Gustavo Fabbro disse...

Felipe

Esse erro que você está tendo certamente está relacionado com as bibliotecas LIBEAY32 e SSLEAY32.

Também uso o INDY9 em meus projetos mas as DLLs que distribuo não tem número de versão anotado para eu ver se as que você tem estão ok para INDY9.

Algumas coisas que você pode verificar:
- Se ambas as DLLs estão acessíveis ao seu programa, isto é, se estão no "caminho" (PATH) do Windows. Melhor ainda se estiverem na mesma pasta.

- Não sei se a versão 1.X delas é compatível com o INDY9. Experimente baixar uma versão anterior para testar. Há algumas versões disponíveis em http://indy.fulgan.com/SSL/.

Anônimo disse...

ola estou tentando carregar um html como imagem so que so manda texto tem como?
falecomdreamsoft@hotmail.com

Luís Gustavo Fabbro disse...

Dê uma olhada no post Enviando emails com Delphi - Parte II. Ele trata do envio de email em formato HTML e tece algumas considerações sobre a inclusão de imagens (e arquivos em outros formatos) no corpo da mensagem.

[]

Anônimo disse...

ola eu fiz o exemplo esta enviando jp gif eu mudo a extensao mais quando coloco html ele adiciona mais fica um quadrado com x como se nao tivesse sido carregado

Luís Gustavo Fabbro disse...

Veja se o que vc colocou como "source" na tag IMG é idêntico ao valor da propriedade FileName do TIdAttachmentFile usado para anexar a imagem.

Há um exemplo no final do post Enviando emails com Delphi - Parte II.

Anônimo disse...

Viva. No meu caso o corpo da mensagem está correcto, mas o subject aparece sem acentuação. os caracteres especiais desaparecem

Luís Gustavo Fabbro disse...

Fiz um teste enviando caracteres acentuados como subject e o email chegou OK. A versão que eu tenho do Indy é a 10 e isso talvez faça diferença - tente atualizar a sua versão.

Pode ser também que haja alguma configuração no servidor de email que vc está usando. Ou então no próprio leitor de email. No teste, usei Exchange como servidor e Outlook como leitor.

Anônimo disse...

boa dia Luis Gustavo, to criando um enviador de boleto para mim para poder facilitar minha vida no dia a dia. mas estou com um problema. quando peço para enviar os emails, ele envia o primeiro email com o primeiro anexo. quando vai no segundo email, envia o primeiro email + o segundo email e assim sucessivamente. tem algum modo de fazer a limpeza de memória do TIdAttachment ? para cada email ir apenas 1 anexo ? embaixo segue os códigos que eu já utilizei, se puder ajudar, fico grato. abç. Rodrigo

Procedure Tfrmenviaemail.btenviarClick(Sender: TObject);
var anexo:integer;
begin
// acessar o primeiro registro da tabela de escritório.
frmdtmdados.tblescritorios.First;

//conectando no servidor
idsmtp.connect;

// fazendo as repetições até o ultimo registro
while not frmdtmdados.tblescritorios.Eof do
begin

edtnmescritorio.Text:=frmdtmdados.tblescritoriosnmescritorio.value;
edtpara.Text:=frmdtmdados.tblescritoriosemailcobranca.value;
edtnmboleto.text:=frmdtmdados.tblescritoriosnmboleto.value;
idmessage.recipients.emailaddresses:=edtpara.text;
idmessage.Subject:=edtnmescritorio.text;
idmessage.body:=mmmensagem.lines;

TIdAttachment.Create(IdMessage.MessageParts,'D:\documentos\projetos\prjcontrole\boletos\boleto1.pdf');

// lbanexos.Items.Text:=frmdtmdados.tblescritoriosnmboleto.value;
// tidattachment.Create(idmessage.MessageParts,tfilename(frmdtmdados.tblescritoriosnmboleto.value));


// teste novo codigo
//TIdAttachment.Create(IdMessage.MessageParts, TFileName(lbanexos.Items.Strings[lbanexos.itemindex]));

// for anexo:=0 to lbanexos.items.count-1 do
// tidattachment.create(idmessage.messageparts,tfilename(lbanexos.Items.strings[anexo]));

// idsmtp.connect;
// try
idsmtp.Send(idmessage);
// finally
// idsmtp.disconnect;
// end;
frmdtmdados.tblescritorios.next;
sleep(3000);
end;
idsmtp.disconnect;
application.messagebox('Emails enviados com sucesso !','confirmação',MB_iconinformation+MB_ok);
end;

Anônimo disse...

Luís Gustavo, já consegui o que queria , então caso queira deixar a questão aí é só adicionar a linha de código .

IdMessage.MessageParts.Clear;

antes de iniciar uma nova mensagem
abç;
Rodrigo

Unknown disse...

Cara...
Muito obrigado pela dica!
Uns carinhas de uma empresa terceirizada fizeram uma manutenção aqui no SMS e no Exchange e lascou o envio de e-mail do sistema de missão crítica que tomo conta.
Eu já tava maluco pesquisando como resolver e graças a sua dica de autenticação usando SSL consegui resolver o problema.

Abraço

Anônimo disse...

Luiz Gustavo Boa Tarde

Estou usando de codigo a baixo:
TIdAttachment.Create (IdMessage.MessageParts, Local.Text');

Onde Local.Text é um Edit que contém a seguinte estrutura:
C:\sisglog\Notas Fiscais\12\Junho\NFe51120607421604000312550010000006891072010162.PDF

e responde que não encontra o arquivo. Ja até digitei toda esta estrutura no local de Local.Text e a mensagem do delphi 7 é a mesma que não encontra o arquivo e o mesmo esta.

Pois preciso informar este arquivo automaticamente dependendo da chave da NFe. Pois quando o usuario confirma o Protocolo o sistema ja enviará a NfeProc. De Acordo com o Manual.

Grato pela ajuda.

Valtino de Oliveira
emails:valtino_fcriminal@hotmail.com

Unknown disse...

Tem como mandar o mesmo e-mail para uma lista de contatos? Analisando o código, parece que só se pode enviar o e-mail para um contato e os cc e cco.

Luís Gustavo Fabbro disse...

Jackson

Você pode fornecer uma lista de destinatários na propriedade EMailAddresses - basta colocar uma vírgula separando cada endereço. Veja a documentação em http://www.iternet.biz/Knowledge/Indy9/007308.html#0002.

[]s

Luís Gustavo Fabbro disse...

Valtino

Esse erro só é reportado se de fato o arquivo não pode ser aberto. Ele pode não existir mesmo mas pode ser tb que o arquivo esteja aberto com acesso exclusivo por outro programa ou que o usuário não tenha permissão de acesso à pasta em questão.

[]s

poesias disse...

Olá Luis.
Agradeço muito a você, seu exemplo se encaixou perfeitamente em um projeto que utilizo uma conta do gmail, porém em outro, como estou dentro de uma rede de uma empresa grande, eles utilizam o exchange e não tem um smpt.gmail.com, o deles é correio.grupovt.com. Nao consigo enviar o e-mail pelo gmail de dentro da rede e nem colocando o servidor exchange. Voce ja viu alguma coisa desse tipo?
Meu contato é pedro@atimam.com.br
Grato

Luís Gustavo Fabbro disse...

Pedro

O tipo de servidor é indiferente. Se ele implementa o protocolo STMP, o envio de emails deve seguir a mesma lógica exposta no post.

Isso significa que provavelmente há regras na infraestrutura de rede da empresa barrando sua tentativa de envio. Envolva o suporte de rede na solução do problema; eles devem ter ferramentas para monitorar o tráfego e saber que tipo de mudança deve ser feita (ou em seu programa ou na própria rede - como rever Firewalls, relays e afins).

Para facilitar, acrescente a seu programa um TIdLogDebug e implemente-o para mostrar toda a comunicação enviada e recebida pelo envio de email.

[]s

Anônimo disse...

Luís, muito obrigado pela resposta.

Agora consegui identificar melhor o problema. O que está ocorrendo é que o servidor exchange exige um certificado. O pessoal da infraestrutura me passou 2 certificados, um raiz e um outro, ambos arquivos .cer. Então eu adicionei esses certificados, respectivamente às propriedades CertFile e RootCertFile, no entanto dá o seguinte erro: "could not load certificate". Já revirei os foruns e não encontrei nada sobre...

Se voce tiver um a luz... ficarei agradecido.

abraços.
Pedro

Luís Gustavo Fabbro disse...

Pedro

Suponho que você já verificou se o caminho completo dos arquivos de certificado foi informado e que estão acessíveis ao usuário Windows do programa (em serviços do Windows, o usuário padrão tem privilégios de acesso restritos).

Outra coisa que você deve checar é se os certificados exigem senha para serem carregados (o que é normalmente o caso). Para passar uma senha na carga, use o evento OnGetPassword do IdSSLIOHandlerSocket.

[]s

Unknown disse...

Cara, que beleza! Apenas com seus artigos consegui primeiro receber e depois enviar e-mail com anexo que era o que eu queria mesmo. Parabéns pela qualidade dos artigos.

Igor Bastos disse...

Estou com problemas para anexar: TIdAttachment.Create (IdMessage.MessageParts, 'c:\temp\arq.doc');
ele envia várias linhas de texto e nada de enviar arquivos!

Luís Gustavo Fabbro disse...

Igor

Não me lembro de ter encontrado esse comportamento antes. Se a função foi montada na sequência correta e com dados do anexo válidos (o caminho e permissões de acesso, por exemplo), não deveria haver maiores dificuldades.

Você pode verificar se não há restrição ao tamanho do anexo no servidor de envio ou no que vai receber o email. Ainda, por questões de segurança, o servidor pode ser configurado para barrar o envio de certos tipos de anexos. O mesmo é válido para o servidor que receberá o email, principalmente se há um antivírus agindo nele.

[]s

Felipe Basso disse...

Amigo boa tarde,

Estou com problemas com o envio de email. Utilizo Indy10 e o código abaixo executa normalmente, porém quando ele tenta enviar pelo comando IdSMTP.Sender(IdMessage); o programa não responde mais. Abaixo está o código, veja por favor se há algo que eu esteja ou não fazendo de errado. Obrigado,

--------------------------
with IdSMTP do
begin
Disconnect;

AuthType := atNone;
Host := 'smtp.gmail.com';
IOHandler := IdSSLIOHandlerSocketOpenSSL;
Password := wSenha;
Port := 587;//465;
ConnectTimeout := 60000;
//UseTLS := utUseExplicitTLS;
end;

with IdMessage do
begin
Body.Add('corpo da mensagem');

From.Address := 'felipen.basso@gmail.com'; //opcional
From.Name := 'NOME'; //opcional

Recipients.Add;
Recipients.Items[0].Address := 'felipe.basso@grupoadmseg.com.br';
Recipients.Items[0].Name := 'DESTINATARIO'; //opcional
Subject := 'Teste De Envio Automático de Email';
end;

try
IdSMTP.Connect();

IdSMTP.SendCmd('STARTTLS', 220);

//IdSSLIOHandlerSocketOpenSSL.PassThrough := False;

IdSMTP.Send(IdMessage);
IdSMTP.Disconnect;
except
on e : Exception do
begin
ShowMessage('Falha no envio!' + e.message);
exit;
end;
end;
--------------------------

Luís Gustavo Fabbro disse...

Felipe

Como vc está usando acesso seguro (SSL), é muito provável que o problema esteja na versão da biblioteca do OpenSSL. Tente garimpar outra versão, apropriada para a sua versão do Delphi. Veja, por exemplo, http://indy.fulgan.com/SSL/

Fábio disse...

Fábio
Boa Tarde amigo.
Tenho uma aplicação de envio de emails com anexos e tb to com o problema no smtp.Send(IdMessage) uso o indy 10 com delphi 7 e ja tentei de tudo. As dlls são pra colocar na pasta c:\windows ? ou no delphi?

Fábio disse...

as dlls é pra colocar no c:windows ou no delphi? estou com problemas tb no smtp.Send(IdMessage).

Luís Gustavo Fabbro disse...

Fábio

As bibliotecas podem ser colocadas na pasta System32, onde serão compartilhadas por todas as aplicações. No entanto, o melhor é colocar na mesma pasta onde está o seu executável pois assim outras aplicações que precisarem de versões diferentes dessas DLLs não terão problemas.

Que tipo de problema o seu envio está apresentando? Há alguma mensagem de erro?

[]s

Fábio disse...

Oi amigo. Obrigado por ter respondido.
Enfim, tem o erro de "connection closed gracefully" quando chega no send. Aí vai o código:

IdMessage := TIdMessage.Create (Nil);
Progresso.Visible := True;
smtp.AuthType := satDefault;
smtp.Host := edit1.Text;
smtp.Username := Edit2.Text;
smtp.Password := Edit8.Text;
smtp.Connect(); //Conecta com o servidor smtp
smtp.Authenticate;
Progresso.Progress := 25;
smtp.IOHandler := IdSSLIOHandlerSocketOpenSSL1;
IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Method := sslvTLSv1;
IdSSLIOHandlerSocketOpenSSL1.PassThrough := True;
smtp.SendCmd('STARTTLS', 220);
smtp.AuthType := satDefault;
IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Method := sslvSSLv2;
IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Mode := sslmUnassigned;

ShowMessage('SMTP Conectado');

IdMessage.Clear;
IdMessage.MessageParts.Clear;
IdMessage.From.Address := Edit2.Text;//O seu endereço de e-mail
Progresso.Progress := 50;
IdMessage.Recipients.EMailAddresses := edit4.Text;//E-mail destino
IdMessage.From.Name := Edit5.Text;
IdMessage.Subject := edit6.Text; //O assunto da mensagem
IdMessage.ContentType := 'Multipart/Related';
IdMessage.Priority := mpHighest;
Progresso.Progress := 75;

TIdAttachmentFile.Create(IdMessage.MessageParts, TFileName(edit7.Text)); Progresso.Progress := 90;
ShowMessage('Vai enviar');
smtp.Send(IdMessage);
Progresso.Progress := 100;
ShowMessage('Email com Arquivo Enviado as ' + TimeToStr(Time)); Progresso.Progress := 0;
showmessage('antes de desconectar');
smtp.Disconnect;

quando copio dlls no system32 da um erro de q não pode copiar pq esta sendo usado por outra aplicação. ja tentei as smtp do gmail, yahoo, hotmail e por enquanto nada.
Obrigado desde já.

Fábio disse...

Olá Caro Luis.

Problemas de "connection closed gracefully" ou "software caused connection abort" ou qualquer coisa do tipo no smtp.Send(IdMessage).

IdMessage := TIdMessage.Create (Nil);
Progresso.Visible := True;
smtp.AuthType := satDefault;
smtp.Host := edit1.Text;
smtp.Username := Edit2.Text;
smtp.Password := Edit8.Text;
smtp.Connect(); //Conecta com o servidor smtp
//smtp.Authenticate;
Progresso.Progress := 25;
smtp.IOHandler := IdSSLIOHandlerSocketOpenSSL1;
IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Method := sslvTLSv1;
IdSSLIOHandlerSocketOpenSSL1.PassThrough := True;
smtp.SendCmd('STARTTLS', 220);
//smtp.AuthType := satDefault;
IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Method := sslvSSLv2;
IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Mode := sslmUnassigned;

//ShowMessage('SMTP Conectado');

IdMessage.Clear;
IdMessage.MessageParts.Clear;
IdMessage.From.Address := Edit2.Text;//O seu endereço de e-mail
Progresso.Progress := 50;
IdMessage.Recipients.EMailAddresses := edit4.Text;//E-mail destino
IdMessage.From.Name := Edit5.Text;
IdMessage.Subject := edit6.Text; //O assunto da mensagem
IdMessage.ContentType := 'Multipart/Related';
IdMessage.Priority := mpNormal;
Progresso.Progress := 75;

TIdAttachmentFile.Create(IdMessage.MessageParts, TFileName(edit7.Text)); Progresso.Progress := 90;
ShowMessage('Vai enviar');
//try
refresh;
smtp.Send(IdMessage);
//smtp.Disconnect;
//except
//ShowMessage('Erro no envio.');
// Application.Terminate;
//end;

Simplesmente não envia nada, nem texto e nem anexo. Desde já agradeço o retorno.

Luís Gustavo Fabbro disse...

Fábio

Alguns problemas que vi em seu código:
=> Sua versão do Indy já possui tratamento nativo para TLS; então, basta ajustar esta opção, sem enviar o comando manualmente.
=> Não sei se você informou a porta para comunicação no form; via fonte, isso não está sendo feito. Para o GMail, o valor é 587.
=> Para ter efeito, as configurações de segurança (SSL/TLS) devem ser feitas antes da conexão com o servidor. Como você está configurando depois, a autenticação falha, derrubando a conexão já estabelecida. É esse efeito que gera a mensagem “connection closed gracefully”, isto é, a conexão está encerrada quando tenta enviar a mensagem.

O código abaixo é uma versão reorganizada daquele que você postou:

var lIdMessage : TIdMessage;
begin
lIdMessage := TIdMessage.Create (Nil);
smtp.AuthType := satDefault;
smtp.Host := Edit1.Text;
smtp.Port := 587; { a porta para conexão }
smtp.Username := Edit2.Text;
smtp.Password := Edit8.Text;

smtp.IOHandler := IdSSLIOHandlerSocketOpenSSL1;
IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Method := sslvTLSv1;
smtp.UseTLS := utUseRequireTLS;

smtp.Connect(); //Conecta com o servidor smtp

lIdMessage.Clear;
lIdMessage.MessageParts.Clear;
lIdMessage.From.Address := Edit2.Text;//O seu endereço de e-mail
lIdMessage.Recipients.EMailAddresses := Edit4.Text;//E-mail destino
lIdMessage.From.Name := Edit5.Text;
lIdMessage.Subject := Edit6.Text; //O assunto da mensagem
lIdMessage.Body.Text := 'teste';
// lIdMessage.ContentType := 'Multipart/Related';
lIdMessage.Priority := mpNormal;

TIdAttachmentFile.Create(lIdMessage.MessageParts, 'c:/temp/nfe.xml');
smtp.Send(lIdMessage);
smtp.Disconnect;


[]s

Fábio disse...

Caro Luís Gustavo.

Muito obrigado pela ajuda e realmente não estava "enxergando" certos deslizes no meu código. Funcionou perfeitamente. Agora só mais um detalhe: Como usar uma barra de progresso durante o envio de email? Tipo durante o send? Desde já agradeço a colaboração e mais uma vez obrigado.

Luís Gustavo Fabbro disse...

Fábio

Veja os eventos OnWork, OnWorkBegin e OnWorkEnd do componente SMTP; eles são responsáveis por notificar o andamento da comunicação nos componentes do Indy. Veja tb o OnStatus para obter mensagens sobre a tarefa atualmente em execução.

[]s

José Mário Silva Guedes disse...

Olá Luís!

Passando para agradecer, mais uma vez, seus artigos.

Me ajudou mais uma vez.

Forte abraço!

Unknown disse...

Prezados,

Utilizo o Delphi 2007 com o Indy e gostaria de algum exemplo para enviar email com o delphi (idSMTP) utilizando um servidor Exchange.

Alguém poderia me ajudar, por favor?

Abraços

Luís Gustavo Fabbro disse...

Roma

O código que está no post funciona também para Exchange. Que tipo de problema vc encontrou ao implementá-lo?

[]s

Unknown disse...

Olá Luis, Gostei muito de seu artigo para enviar E-mail, estou a procura de como enviar Sms, pode ajudar . Desde já Agradecido.

Luís Gustavo Fabbro disse...

Alex

Nunca trabalhei com envio de SMS mas até onde eu sei, vc precisará contratar um serviço de gateway para fazer o meio de campo entre seu sistema e os servidores das operadoras de celular. Normalmente, é disponibilizado um endereço de email ou uma URL para receber a solicitação de envio do SMS; no primeiro caso, envia-se um email simples e, no segundo, vc posta a mensagem via HTTP. Veja essa discussão no fórum da Info: http://info.abril.com.br/forum/viewtopic.php?t=9472.

[]s

Unknown disse...

Teria Um exemplo para Download?
do envio de e-mail

Luís Gustavo Fabbro disse...

Eduardo

Você pode recuperar os e-mails de uma caixa postal com POP3 ou com IMAP. Há posts no blog sobre ambos os protocolos: Recuperando emails de uma caixa postal com Delphi e Trabalhando com emails em uma caixa postal usando IMAP4 em Delphi.

[]s

Flávio Nascimento disse...

Olá Luís, boa tarde!
Com meu código funcionou para o gmail mas ao mudar para empresa que usa office 365 via smtp deu o erro: "Error Connecting with SSL. Error connecting with SSL. error: 1408F10B:SSL routines:SSL3_GET_RECORD:wrong version number". Tem alguma ideia do que possa ser? Abaixo meu código:
function EnviarEmail(aHost : String; aPort : Integer; aLogin, aSenha,aListaEmail, aAssunto, aCorpo : String; aAuth, aAuthSSL : Boolean) : Boolean;
var
AuthSSL : TIdSSLIOHandlerSocketOpenSSL;
IdSMTP : TIdSMTP;
IdMessage : TIdMessage;
Const
ArqTeste = 'c:\teste.txt';
begin
Result:= False;
IdSMTP := TIdSMTP.Create(nil);
IdMessage := TIdMessage.Create(nil);
try
IdSMTP.Host := aHost;
IdSMTP.Port := aPort;
IdSMTP.AuthType := satDefault;
IdSMTP.Username := aLogin;
IdSMTP.Password := aSenha;
if aAuthSSL then
begin
AuthSSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
IdSMTP.IOHandler := AuthSSL;
IdSMTP.UseTLS := utUseImplicitTLS;
AuthSSL.SSLOptions.Method := sslvSSLv3;
AuthSSL.SSLOptions.Mode := sslmClient;
end;
if aAuth then
IdSMTP.AuthType := satDefault
else
IdSMTP.AuthType := satNone;
try
IdSMTP.Connect;
except
IdSMTP.Connect;
end;

try
IdMessage.From.Address := aLogin;
IdMessage.Recipients.EMailAddresses := aListaEmail;
IdMessage.Subject := aAssunto;
IdMessage.Body.Text:= 'Data/Hora: ' + FormatDateTime('dd/MM/yyyy HH:mm:ss', Now) + #13#10#13#10 +aCorpo;
IdMessage.Body.SaveToFile(ArqTeste);
IdMessage.MessageParts.Clear; // Limpa os anexos da lista
TIdAttachmentFile.Create(IdMessage.MessageParts, TFileName(ArqTeste)); // adiciona anexo na lista, pode ser utilizado com looping
IdSMTP.Send(IdMessage);
ShowMessage('E-Mail Enviado com sucesso para: ' + aListaEmail);
except
on E: Exception do
begin
ShowMessage('Erro ao enviar E-Mail:'+#13#10+e.Message);
Exit;
end;
end;
finally
IdSMTP.Disconnect;
FreeAndNil(IdSMTP);
FreeAndNil(IdMessage);
if AuthSSL <> nil then
FreeAndNil(AuthSSL);
end;
Result:= True;
end;

Luís Gustavo Fabbro disse...

Flávio

Pela mensagem de erro, o Office 365 parece não trabalhar com a versão 3 do SSL. Experimente usar uma versão anterior (2.1 ou 2) ou ainda usar TLS no lugar do SSL. Basta substituir a atribuição feita na linha

AuthSSL.SSLOptions.Method := sslvSSLv3;

pelo valor correspondente ao esquema de segurança apropriado.

OBS: Provavelmente, a opção de segurança terá que ser configurável em seu sistema pra que ele funcione corretamente, de acordo com o provedor de email de cada Cliente.

[]s

Flávio Nascimento disse...

Olá Luís,

Fiz essas alterações:
IdSMTP.UseTLS := utUseExplicitTLS;
AuthSSL.SSLOptions.Method := sslvTLSv1;
AuthSSL.SSLOptions.Mode := sslmClient;

E funcionou, porém ficou extremamente lento o envio na linha IdSMTP.Send(IdMessage)
Como a quantidade de e-mails é mto grande, acho que vou fazer através de thread para evitar problemas.
De qualquer forma, você mais uma vez ajudou muito.

Obrigado!

Flávio Nascimento disse...

Olá Luís,

Se puder me auxiliar mais uma vez... Estou conseguindo fazer o envio através de Threads sem problemas mas ocorre que a empresa tem um número limite de contas para criar na microsoft no office 365. Então vou usar uma conta genérica para fazer envio de faturamento e cobrança, mas mesmo eu alterando o displayname (IdMessage.From.DisplayName), continua aparecendo o nome da minha conta para o destinatário. Ex: conta e-coleta, responde para o endereço que determino mas na tela do destinatário aparece mesmo "e-coleta". Uso delphi 2010. Tem alguma sugestão do que pode ser feito? Também mudei IdMessage.From.Name, IdMessage.From.Text, IdSMTP.MailAgent, IdSMTP.HeloName, tudo sem sucesso.

Grato
Flávio

Luís Gustavo Fabbro disse...

Flávio

Por questão de segurança da internet, o nome/endereço do remetente do email deve ser aquele que de fato está fazendo o envio. Se não for assim, você poderia enviar spam nome de outra pessoa, se passando por ela.

Você pode trocar o nome que aparece por um apelido usando a propriedade lIdMessage.From.Name mas o endereço real utilizado aparecerá para o destinatário.

Se o que você quer é que o destinatário possa responder a um endereço diferente, use lIdMessage.ReplyTo. Se a intenção é enviar o email em nome de outra pessoa, alguns servidores permitem delegar o envio. Para outlook, há um exemplo de como fazer a configuração neste link.

[]s

Flávio Nascimento disse...

Agradeço mais uma vez Luís pelo auxílio de sempre.

Unknown disse...

Olá, primeiramente parabéns pela post, mesmo que já fazendo um bom tempo.
Procurei em tudo que é parte e não encontrei: É possível salvar o e-mail enviado na pasta 'Enviados' ou 'Sent'?
Tentei logo após o envio dar um IdIPOP3.SendMsg(msg); porem ele não salva.
Isso é possível?
Obrigado.

Luís Gustavo Fabbro disse...

Tiago

O POP3 é bastante simples e não permite trabalhar com pastas. Para tanto, use o protocolo IMAP4 - há um post no blog sobre o funcionamento dele. Especificamente sobre a cópia de mensagens, você pode usar a função CopyMsgs da implementação de IMAP34 do Indy.

[]s

Unknown disse...

Muito obrigado pela ajuda.

Segue o código, pois andei lendo tem várias pessoas procurando, espero que ajude:

msg := TidMessage.Create(self);
msg.LoadFromFile(Caminho_do_arquivo);
IdIMAP4.AppendMsg('INBOX.Sent', msg, msg.Headers, []);
msg.Free;

Obrigado.

Unknown disse...

Na realidade, verificando, não funciona 100% da maneira que precisava.
Envio os emails com o ContentType := 'multipart/alternative'; contendo duas partes: 1ª IdText.ContentType := 'text/plain' e 2ª IdText.ContentType := 'text/html';
Envia 100% porém, utilizo o seguinte para mandar para a pasta Sent:
IdMessage.GenerateHeader;
IdIMAP4.AppendMsg('INBOX.Sent', IdMessage,
IdMessage.LastGeneratedHeaders, []);

Envia para a pasta Sent porém, não aparece a mensagem!
Verificando o header da mesma, no cabeçalho fica Content-Type: multipart/alternative; charset=utf-8; boundary="8m=_RpThyp9uCns6TJpPnuctGZjSdbUSWH"
e no corpo da mensagem --v7s=_u7vUVrDyktVaa3VyDMlOC4Z4TNSCz
sendo que na caixa de entrada, os dois valores ficam iguais.
Alguém teria uma solução para este problema?
Obrigado.

Unknown disse...

Olá Luis

Como faço para anexar um Stream?

Obrigado

Valdir

Luís Gustavo Fabbro disse...

Validr

A versão 10 do Indy introduziu mais uma classe para criação de anexos chamada TIdAttachmentMemory. O construtor dela aceita como parâmetro a passagem de heranças de TStream ou TIdStream. Então, basta criar uma instância do TIdAttachmentMemory (ao invés de TIdAttachment), passando a ela o stream que você já tem.

[]s

Unknown disse...

Obrigado Luís

Reinaldo disse...

Oi Luis! Parabéns pelo Blog!

Tem como saber se o e-mail enviado pela aplicação realmente foi recebido pelo destinatário do e-mail. Tive um problema por motivo de configuração do e-mail no Outlook das mensagens não serem enviadas e não ocorre erro. Foi criado um grupo com vários e-mail para esse envio.

Desde já agradeço,

Reinaldo

Luís Gustavo Fabbro disse...

Reinaldo

Não sei se pelo SMTP tem como você solicitar essa confirmação. Mas, você pode usar integração direta com o Outlook (veja um exemplo aqui do blog mesmo). Uma das propriedades do objeto "Email" do outlook é a OriginatorDeliveryReportRequested que notifica o remetente quando a mensagem for entregue na caixa postal do(s) destinatário(s).

[]s

Anônimo disse...

Luís,
estamos com problema de autenticação Exchange. Testamos com outro componente e conseguimos conectar usando dominio.int\nome@dominio.com.br. Mas, se configuramos isso no IdSMTP.username dá erro de endereço inválido. Você já viu este tipo de configuração usando o Indy?

Luís Gustavo Fabbro disse...

Mara

Pode ser que o cenário no qual você está trabalhando exija um tipo de autenticação diferente daquele que eu usei no post.

Experimente colocar o AuthType do IdSMTP com o valor atSASL pra ver se funciona.

Chegou a acrescentar logs do próprio Indy para ver em que ponto falha? Qual a mensagem exata reportada pelo componente?

[]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.