9 de julio de 2014

Mas sobre el envio de mensajes de correo electrónico desde Visual FoxPro

Existen muchas herramientas y formas de enviar mensajes de correo electrónico desde Visual FoxPro, y seguramente por ello, existen muchos artículos y códigos escritos sobre este tema. La razón de escribir mas de los mismo, es comentarles sobre mi experiencia y elección personal, de la herramienta que utilizo actualmente, la cual me satisface y cubre todas mis necesidades. Estoy convencido que lo mismo le sucede a muchos desarrolladores y espero que al terminar de leer estas líneas, esto ayude a muchos mas.

The winner is ...

Como indico arriba, esto es "mi experiencia y elección personal", obviamente no digo que sea "la mejor", ni digo tampoco "la peor", por lo tanto no entraré en comparación con otras herramientas, solo hablaré de CDO.

¿Qué es CDO?

CDO (Collaboration Data Objects) es un componente COM (Component Object Model) que simplifica la escritura de código para crear y manipular mensajes de Internet y es parte integrante (Cdosys.dll) de los sistemas operativos Windows 2000 y superiores, siendo esta la primer gran ventaja, ya que no necesitamos descargar, comprar, ni licenciar, ninguna otra herramienta extra. CDO no necesita que tengamos un servidor SMTP local, solo necesita acceso a la Web, a algún servidor SMTP que nos permita enviar los mensajes de correo electrónico.

Usando VFP

Otra ventaja adicional, es que se puede utilizar CDO con lenguajes que soporten COM y Automation, y Visual FoxPro lo soporta. A continuación un sencillo código para enviar un mensaje de correo electrónico con CDO desde VFP. Recuerde configurar correctamente el nombre del servidor SMTP, el puerto SMTP, el nombre de usuario y contraseña.
loCfg = CREATEOBJECT("CDO.Configuration")
WITH loCfg.Fields
  .Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "smtp.mail.com"
  .Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
  .Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
  .Item("http://schemas.microsoft.com/cdo/configuration/sendusername") = "user@mail.com"
  .Item("http://schemas.microsoft.com/cdo/configuration/sendpassword") = "password"
  .Update
ENDWITH

loMsg = CREATEOBJECT ("CDO.Message")
WITH loMsg
  .Configuration = loCfg
  .From = "user@mail.com"
  .To = "user1@mail.com.ar"
  .Subject = "Prueba desde VFP"
  .TextBody = "Este es un mensaje de prueba con CDO desde Visual FoxPro."
  .Send()
ENDWITH

Adjuntando archivos en nuestros mensajes

Para enviar archivos adjuntos en un mensaje de correo electrónico, solo debemos llamar al método AddAttachment() del objeto Message, tantas veces como archivos deseamos adjuntar, con la ruta y el nombre del archivo como parámetros:
WITH loMsg
 .AddAttachment("C:\Imagenes\Foto1.jpg")
 .AddAttachment("C:\Imagenes\Foto2.jpg")
 .AddAttachment("C:\Imagenes\Foto3.jpg")
ENDWITH

Autenficación y cifrado SSL

Algunos servidores SMTP, necesitan autentificación y cifrado SSL para iniciar sesión. Para ello debemos configurar las propiedadades smtpauthenticate y smtpusessl del objeto Configuration con el valor .T..
WITH loCfg.Fields
  .Item("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = .T.
  .Item("http://schemas.microsoft.com/cdo/configuration/smtpusessl") = .T.
ENDWITH

Mensajes a múltiples destinatarios

Para enviar el mensaje de correo a mas de un destinatario, solo debemos separar las direcciones de correos electrónicos, con el caracter "," (coma) en la propiedad To. También podemos agregar una copia para otro destinatario, como así también una copia oculta, configurando las propiedades Cc y Bcc respectivamente.
WITH loMsg
 .To = "user1@mail.com, user2@mail.com"
 .Cc = "user3@mail.com"
 .Bcc = "user4@mail.com"
ENDWITH
Podemos hacer que aparezca el nombre completo del remitente y/o del destinatario, anteponiendo el nombre a la dirección de correo electrónico, y encerrando ésta última entre "<" y ">"
WITH loMsg
 .From = "Jose Fox <jose@fox.org>"
 .To = "Usuario Uno <user1@mail.com>, Usuario Dos <user2@mail.com>"
ENDWITH

Mensajes con formato HTML

Con CDO muy fácilmente se puede enviar mensajes de correo electrónico con formato HTML, solamente configurando la propiedad HTMLBody con código HTML válido, en lugar de configurar la propiedad TextBody que es válida para los mensajes con texto sin formato.
WITH loMsg
  .HTMLBody = "<p>Este es un texto HTML con <b>negritas</b> o con <i>cursivas</i>.</p>"
ENDWITH
Esta propiedad la podemos configurar con el contenido de un archivo HTML con la función FILETOSTR() como se muestra a continuación.
WITH loMsg
  .HTMLBody = FILETOSTR("C:\Archivo.htm")
ENDWITH
Mas adelante veremos otra manera de dar formato HTML a un mensaje de correo electrónico, a partir de un archivo HTML ubicado en el disco local o la Web.


Solicitando confirmación de lectura

Podemos configurar nuestros mensajes de correo electrónico para solicitar una confirmación de lectura y recibir ésta confirmación en la dirección predeterminada u otra (siempre y cuando el servidor y/o el programa cliente del destinatario tengan habilitada esta opción). El código para solicitar y recibir la confirmación de lectura en la misma dirección de correo del remitente es el siguiente:
WITH loMsg
  .From = "user@mail.com"
  .Fields("urn:schemas:mailheader:disposition-notification-to") = .From
  .Fields("urn:schemas:mailheader:return-receipt-to") = .From
  .Fields.Update
ENDWITH

Marcar los mensajes con importancia y prioridad

Otra opción que nos brinda CDO es configurar la importancia y prioridad de nuestros mensajes de correo electrónico, marcándolos como Normal (por omision), Alta o Baja. Por ejemplo el código para marcar un mensaje con importancia y prioridad Alta es:
WITH loMsg
  *-- Prioridad
  && -1=Low, 0=Normal, 1=High
  .Fields("urn:schemas:httpmail:priority") = 1
  .Fields("urn:schemas:mailheader:X-Priority") = 1
  *-- Importancia
  && 0=Low, 1=Normal, 2=High
  .Fields("urn:schemas:httpmail:importance") = 2
  .Fields.Update
ENDWITH

Mensajes con imágenes

Bueno, hasta aquí todo marcha bien sin mayores complicaciones; ahora solo nos faltaría saber como añadir imágenes a nuestros mensajes de correo con formato HTML.

Una opción simple, es que en el código HTML hagamos referencia a una URL de un sitio donde este almacenada la imagen, de esta forma se mostrará, siempre y cuando, se tenga acceso a internet. Esta opción tiene a favor que el mensaje enviado tiene un tamaño pequeño, ya que las imágenes estan almacenadas en algún lugar de la Web.
WITH loMsg
  .HTMLBody = "<p><img src='http://3.bp.blogspot.com/-z50Tr6VE5DU/VB29tq3p1fI/AAAAAAAABPM/7vUXmTkK7Ns/s873/logo_cvfpe.png'></p>" + ;
    "<p>La imagen de arriba esta ubicada en el sitio de la ComunidadVFP.</p>"
ENDWITH
Otra opción es embebiendo las imágenes en el mensaje de correo. Para ello tenemos direrentes maneras de hacerlo.

Una forma es reemplazando el contenido: SRC='c:\Imagenes\Imagen10.gif' en la etiqueta IMG, por un ID de Contenido, como por ejemplo: SRC='cid:imagen10-gif'. La ruta y nombre del archivo de imagen, como el ID de Contenido, lo pasamos como parámetro al método AddRelatedBodyPart() que agregará un objeto imagen al cuerpo del mensaje.
WITH loMsg
  .HTMLBody = "<p><img src='cid:id_imagen10'></p>" + ;
    "<p>La imagen de arriba esta embebida en el mensaje.</p>"
  loBP = .AddRelatedBodyPart("c:\Imagenes\Imagen10.gif", "id_imagen10", 1)
  WITH loBP.Fields
    .Item("urn:schemas:mailheader:Content-ID") = "id_imagen10"
    .Update
  ENDWITH
ENDWITH
Esta forma de embeber las imágenes en los mensajes de correo electrónico, se utiliza cuando generamos programáticamente toda la cadena HTML y las imágenes son muy pocas. Cuando la cantidad de imágenes aumenta, este método resulta un poco engorroso.

La otra manera es directamente tomar un archivo HTML, ya sea desde la Web o del disco local, con el método CreateMHTMLBody(), y éste convierte automáticamente los enlaces de las imágenes en un ID de Contenido. A continuación vemos la forma de lograr esto con un ejemplo con la opción de si el archivo está almacenado en la Web o en el disco local:
WITH THIS.oMsg
  IF llFileLocal 
    *-- Si el archivo está en el disco local
    .CreateMHTMLBody("file://c:\documentos\archivo.htm", 0)
  ELSE
    *-- Si el archivo está en la Web
    .CreateMHTMLBody("http://comunidadvfp.blogspot.com/p/acerca-de.html", 0) 
  ENDIF
ENDWITH
Este método es válido, aun si el archivo HTML no contiene imágenes, con lo que lo podemos usar para reemplazar:
WITH loMsg
  .HTMLBody = FILETOSTR("C:\Archivo.htm")
ENDWITH
con:
WITH loMsg
  .CreateMHTMLBody("file://C:\Archivo.htm")
ENDWITH
como indicamos mas arriba.

El ejemplo final

Para terminar veremos un ejemplo completo y funcional de como enviar un mensaje de correo electrónico desde Visual FoxPro, con varias de las opciones que mostramos. Vamos a usar una cuenta de Gmail (el servidor SMTP de Gmail requiere autenticación y utiliza cifrado SSL), y enviaremos un mensaje de correo a Usuario Uno, con copia a Usuario Dos, con confirmación de lectura y con formato HTML, tomado de la Web y con imágenes embebidas, y un archivo adjunto que seleccionaremos de nuestro disco.
LOCAL loCfg, loMsg, lcFile, loErr
TRY
  loCfg = CREATEOBJECT("CDO.Configuration")
  WITH loCfg.Fields
    .Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "smtp.gmail.com"
    .Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 465 && ó 587
    .Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
    .Item("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = .T.
    .Item("http://schemas.microsoft.com/cdo/configuration/smtpusessl") = .T.
    .Item("http://schemas.microsoft.com/cdo/configuration/sendusername") = "josefox@gmail.com"
    .Item("http://schemas.microsoft.com/cdo/configuration/sendpassword") = "password"
    .Update
  ENDWITH
  loMsg = CREATEOBJECT ("CDO.Message")
  WITH loMsg
    .Configuration = loCfg
    *-- Remitenete y destinatarios
    .From = "Jose Fox <josefox@gmail.com>"
    .To = "Usuario Uno <user1@gmail.com>"
    .Cc = "Usuario Dos <user2@gmail.com>"
    *- Notificación de lectura
    .Fields("urn:schemas:mailheader:disposition-notification-to") = .From
    .Fields("urn:schemas:mailheader:return-receipt-to") = .From
    .Fields.Update
    *-- Tema
    .Subject = "Ejemplo del " + TTOC(DATETIME())
    *-- Formato HTML desde la Web
    .CreateMHTMLBody("http://comunidadvfp.blogspot.com/p/acerca-de.html", 0)
    *-- Archivo adjunto
    lcFile = GETFILE()
    IF NOT EMPTY(lcFile)
      .AddAttachment(lcFile)
    ENDIF
    *-- Envio el mensaje
    .Send()
  ENDWITH
CATCH TO loErr 
  MESSAGEBOX("No se pudo enviar el mensaje" + CHR(13) + ;
    "Error: " + TRANSFORM(loErr.ErrorNo) + CHR(13) + ;
    "Mensaje: " + loErr.Message , 16, "Error")
FINALLY
  loMsg = NULL
  loCfg = NULL
ENDTRY

Otros enlaces relacionados al uso de CDO con VFP
Notas finales

He tomado la elección de usar CDO para el envío de los mensajes de correo electrónico en mis aplicaciones hace aproximadamente 20 meses. Desde entonces he creado una una clase que encapsula al objeto CDO, y a medida que aparecieron nuevas necesidades, agregaba nuevos métodos y/o propiedades a la definición de la clase, con lo que logre que los cambios no tengan efectos no deseados en mis aplicaciones anteriores. Es por ello que aconsejo a ustedes a que también hagan su propia clase para el envio de mensajes de correo electrónico, con todas sus necesidades, tomándolas de cada ejemplo de este artículo.

[07/04/2015] ACTUALIZACIÓN:

Quizás no puedas enviar correos electrónicos con el código de este artículo dado que Gmail (Google) puede bloquear intentos de inicio de sesión de aplicaciones que no utilicen los últimos estándares de seguridad. Para solucionar este inconveniente, se debe habilitar en nuestra cuenta de Google el acceso a aplicaciones menos seguras. Esto lo haces desde el enlace: https://www.google.com/settings/security/lesssecureapps

Hasta la próxima,


Luis María

21 comentarios :

  1. Gracias por tu aporte, estoy haciendo las pruebas y no envia el mail, simplemente me aparece como si me lo enviara a mi.
    me puedes ayudar ?

    ResponderEliminar
  2. No sé si el comentario anterior se borró, pero quiero agradecerte Luis María por el esfuerzo que te tomas en publicar estos artículos tan explicativos, te comentaba que a mi me han servido mucho a través de los años como desarrollador y te estoy muy agradecido, saludos desde Honduras.

    ResponderEliminar
  3. Gracias, buenos días. Estoy usando este ejemplo y me cancela el proceso en el SEND. El error que saca es OLE IDispatch Exeception code 0? Teh message could not be send to the SMTP server. The transpor t error code was 0x80040217 . The server response was not available. Gracias por la aytuda. Muy amable

    ResponderEliminar
  4. Hola yo tenia ese mismo mensaje al tratar de enviar un correo por medio de Gmail también me aparecia otro mensaje que no preciso ahora, en mi caso no era problema con los parámetros del programa sino con el correo, en Gmail tuve que habilitar una opción que se llama "acceso de aplicaciones menos seguras " y una vez habilitado deje de tener problemas para el envío, ojala les sirva de algo.

    ResponderEliminar
  5. Funciona perfecto muchas gracias....!!!, Como le puedo hacer para que en la bandeja de enviados de mi cuenta de correo aparezca estos mensaje que envió desde el sistema que quede un historial de envio.

    Gracias.

    ResponderEliminar
  6. Como puedo hacer para que me aparezcan aquellos correos que son enviados desde el sistema en la bandeja de enviados?

    ResponderEliminar
  7. Podeis echarme una mano con la configuracion de correo de gmail?

    Gracias

    ResponderEliminar
    Respuestas
    1. Exactamente que necesitas configurar el smtp,el puerto la capa de conexion o un error en especial?

      Eliminar
  8. .Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "smtp.gmail.com"
    .Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 465 && ó


    Cambio el servidor y el puerto por variables publicas, que contienen el servidor y el puerto que están en una tabla pero me muestra error pero lo dejo como esta en el ejemplo y funciona que puede ser

    ResponderEliminar
    Respuestas
    1. El valor de la variable Puerto SMTP es un entero número entero = 465 (u otro como 587 ó 25)

      Eliminar
  9. buenos dias

    Compañero yo utilizo esta forma de enviar correo hace mucho tiempo y todo perfecto, hasta ahora que debo enviar archivos de tipo
    PDF, el proceso se hace todo correcto, pero el correo que llega al destinatario tiene el archivo adjunto dañado
    no lo deja abrir , esto me sucede con archivos pdf, jpgm doc, los archivos txt llegan sin problemas.

    agredezco cualquier alguna ayuda que me puedan brindar .

    gracias

    ResponderEliminar
    Respuestas
    1. Se me presentó el mismo problema y no encontré una solución. Si se ejecuta el ejemplo original, colocando por supuesto los datos apropiados, y se adjunta un archivo de cualquier tipo llega sin problemas (junto con con el resto de la información del ejemplo). Así que analizando mi adaptación Vs el original y la diferencia era la sección de agregar la sección de HTML. Así que agregue esta sección y !voala! funciona perfectamente, Lo que agregue como sección HTML es:
      *-- Formato HTML desde la Web

      .HTMLBody = " Esta Sección la Agrego, para Solventar los Archivos Anexos. Gracias a la Comunidad VFP. etiqueta"

      la palabra etiqueta la agregue por error al publicar
      Espero le sirva también.

      Eliminar
  10. Buenas noches

    Al ejecutar este codigo me sale el error de "el protocolo especificado es desconocido"

    ResponderEliminar
  11. Luis María,

    Muchas gracias por tus aportes.

    Yo probé y todo bien.

    ¿Hay alguna posibilidad de que quede en gmail como borrador?

    Es porque así controlo, antes de enviar, de que esté todo bien.

    Muchas gracias.

    Un gran abrazo desde Uruguay,


    Washington

    ResponderEliminar
  12. Buenas tardes queridos colegas, me gustaria saber por favor si es posible accesar al articulo " Lo nuevo del Generador de Informes en VFP 9.0 Parte 2", Mil gracias por su atencion

    ResponderEliminar
  13. Hola que tal? se me presento el siguiente caso referente a lo que es envio de mail, tengo un sistema que envia correo con un adjunto que lo utilizan varios dispositivos que usan la misma cuenta de correo para hacer el envio, el problema que se me presento que algunos casos el catch no toma ningun error pero el correo no sale o no llega, desde ya gracias por cualquier sugerencia

    ResponderEliminar
  14. Se pueden enviar correos masivos mediante esta técnica? necesito enviar un mail a 5,000 cuentas de correo, gracias Luis Maria si puedes orientarme

    ResponderEliminar