El documento describe los formatos MIME utilizados en los mensajes de correo electrónico. Explica que los mensajes MIME pueden contener múltiples partes alternativas o mezcladas, separadas por límites. También describe cómo crear mensajes de texto plano o HTML y adjuntar archivos usando encabezados como Content-Type, Content-Transfer-Encoding y boundary.
1. Formatos MIME
Formato de los mensajes
de correo electrónico
Las cabeceras MIME de un mensaje
En la página anterior hemos Aquí tienes un ejemplo con los diferentes elementos del encabezado de un mensaje.
hablado acerca de la manera de Como ves, he incluido todos los elementos dentro de la función mail.
enviar un e-mail y veíamos la
forma de insertar el cuarto <?
parámetro de la función mail para mail("juan@mispruebas.com", "Cabeceras", "Prueba de cabeceras",
incluir algunos elementos de los
"Date: 24 de Junio de 2001
encabezados MIME.
MIME-Version: 1.0
El formato de los mensajes está From: Estudiante Perico<perico@mispruebas.com>
especificado en una serie de Cc:perico@mispruebas.com
normas conocidas como el MIME Bcc:andres@mispruebas.com
(Multipurpose Internet Mail Reply-To: perico@mispruebas.com
Extensions) en las que se X-Mailer: PHP/".phpversion());
establecen los contenidos y la ?>
sintaxis de las diferentes partes de
un mensaje.
ejemplo98.php
Recordemos que la función
mail(dest, asunt, men, cab) Una forma un poco más depurada del script anterior podría ser esta que incluimos aquí
debajo.
tiene cuatro parámetros y que las
especificaciones del MIME aluden a Sus particularidades son las siguientes:
los dos últimos, es decir a men (el
cuerpo del mensaje) y cab que es
el encabezado del mismo. s Recogemos los datos en variables e insertamos en la función mail esas variables
s La variable $cabecera tiene algunas singularidades:
Respecto a dest (destinatario) y s La vamos construyendo añadiendo subcadenas: date, from, etc. etc.
asunt no se requieren más s En cada subcadena dejamos pegado el contenido a las comillas iniciales
comentarios que reiterar la s Al final de cada subcadena (cada una contiene un elemento del encabezado)
necesidad de incluir esos valores insertamos n para el carácter especial que indica a PHP un salto de línea
(e-mail del destinatario y asunto) imprescindible
bien directamente, como
parámetro en la función, o a través
de una variable tal como hemos
comentado en la página anterior.
<?
# datos del mensaje
Cabeceras de los mensajes $destinatario="juan@mispruebas.com";
$titulo="Cabeceras en variables";
Los diferentes elementos de la $mensaje="Nueva prueba de cabeceras";
cabecera de un mensaje deben $responder="andres@mispruebas.com";
insertarse siempre separados por $remite="andres@mispruebas.com";
saltos de línea bien pulsando $remitente="Otra vez Andrés";
Enter o incluyendo la secuencia n # cabeceras
dentro de la misma de línea.
$cabecera ="Date: ".date("l j F Y, G:i")."n";
No pueden incluirse espacios, ni al $cabecera .="MIME-Version: 1.0n";
comiezo de las nuevas líneas ni $cabecera .="From: ".$remitente."<".$remite.">n";
después de n, y las comillas –que $cabecera .="Return-path: ". $remite."n";
han de contener todo el $cabecera .="X-Mailer: PHP/". phpversion()."n";
encabezado– se abren delante del
primero de ellos y no se cierran if( mail($destinatario, $titulo, $mensaje,$cabecera)){
hasta después de haber escrito el echo "mensaje enviado";
último. }
Pueden contener lo siguiente: ?>
Date: xxxxx
ejemplo99.php
Date: debe escribirse con esta
sintaxis exactamente.
El parámetro xxxxx es una cadena Algunas funciones PHP que incorporamos en estos ejemplos
que contendrá la fecha de envío
del mensaje y que puede
obtenerse a través de una de las Podrás ver en estos ejemplos algunas funciones raras que vamos a comentar
funciones de fecha de PHP tal seguidamente:
como puedes ver en el ejemplo.
uniqid(pre,bol)
MIME-Version: 1.0
Genera un identificador único basado en la hora actual del sistema expresada en
Este elemento de la cabecera
microsegundos y con una longitud de 13 caracteres.
especificará la versión MIME que
ha de utilizar el cliente de correo
El parámetro pre permite establecer una cadena o número (puede ser una cadena vacía)
para poder interpretar
adecuadamente el contenido de los que se antepone al identificador generado por la función.
mensajes.
Opcionalmente permite el segundo parámetro bol que debe ser un valor booleano (TRUE
From: remitente<e-mail> ó FALSE) o también 0 ó 1.
Este elemento de la cabecera Cuando este parámetro es TRUE añade al final de la cadena generada anteriormente otra
permite indicar el nombre del subcadena numérica -generada aleatoriamente- de nueve dígitos, que refuerza la unicidad
remitente (remitente) y su del identificador.
dirección e-mail siguiendo la
sintaxis que se especifica. El eregi_replace(busca, reemplaza, cadena)
nombre, como un elemento
independiente y la dirección e-mail Busca en la cadena especificada en el parámetro cadena (que puede ser una cadena o
dentro de < >. una variable que contenga una cadena) las subcadenas especificadas en busca (pueden ser
¡Cuidado! expresiones regulares) y sustituye esas subcadenas por el contenido del parámetro
reemplaza.
2. No debemos poner comillas ni en
el nombre del remitente, ni en la Esta función devuelve la cadena modificada.
dirección e-mail, ni en la fecha,
etcétera. strip_tags(cadena, excepciones)
Respecto a Cc: y Bcc: ; Reply-To: Suprime todas las etiquetas HTML contenidas en cadena salvo las que se indiquen en
y X-Mailer: son válidos los
excepciones.
comentarios que hemos hecho en
la página anterior.
Por ejemplo: strip_tags($cadena, '<i><u><b>') eliminaría todas las etiquetas HTML,
Si no se especifica lo contrario, los salvo las indicadas aquí y sus correspondientes cierres.
mensajes se envían como texto
sin formato, pero existen Si no se especifican excepciones elimina todas las etiquetas.
opciones que permiten especificar
el formato que ha de tener un base64_encode(cadena)
mensaje.
Devuelve una cadena codificada en base64. Esta codificación se hace para permitir que
La especificación de un formato las informaciones binarias puedan ser correctamente manipuladas por sistemas que no
obliga a incluir otro elemento en generan correctamente los 8 bits, tal como ocurre frecuentemente en los cuerpos de los
cabecera del mensaje: mensajes de correo electrónico.
Content-Type: base64_decode(cadena)
Este elemento debe ir seguido de
Realiza el proceso inverso a la anterior. Decodifica una cadena previamente codificada
la especificación en la que se
indique el tipo de contenido. Tiene en base64.
la sintaxis: tipo/subtipo
chunk_split(cadena, longitud, separador)
El MIME establece un gran
variedad de opciones para este Devuelve una cadena obtenida al insertar en la cadena especificada -a intervalos del
propósito. Hablaremos de dos de número de caracteres especificados en el parámetro numérico longitud- el contenido de
ellas: una subcadena indicada en el parámetro separador.
text/plain Por defecto -cuando no se especifican los parámetros- longitud es igual a 76 caracteres
y el separador es la cadena rn (retorno y salto de línea).
El text/plain es la opción por
defecto y señala que el contenido Esta función se utiliza para convertir al formato especificado en la RFC 2045
del mensaje es de tipo texto
(especificación para MIME) las cadenas obtenidas por base64_encode.
(text) y del subtipo sin formato
(plain)
Es el formato habitual de los ficheros adjuntos de los e-mail.
text/html
Como la opción anterior, es tipo Mensaje con contenido alternativo
texto, pero en este caso, el
subtipo es html con lo cual el
mensaje se visualizará en formato
html siempre que el cliente de <?
correo permite esa posibilidad. # creamos la variables "salto" para "mayor comodidad
# un salto es la secuencia retorno de carro-nueva línea
Mensajes multiparte # dos saltos es algo similar pero duplicado
$UN_SALTO="rn";
Los tipos anteriores permiten
enviar mensajes simples (sin
$DOS_SALTOS="rnrn";
ficheros adjuntos) en uno u otro
formato, pero el MIME nos da # creamos el remitente, etc. y también la que parte que
opciones para insertar dentro de # contiene el código HTML del mensaje
un mismo mensaje elementos de
diferentes tipos y subtipos. $destinatario="juan@mispruebas.com";
$titulo="Mensaje alternativo Texto Plano - HTML ";
Las opciones de mayor interés son $mensaje="<html><head></head><body bgcolor='#ff0000'>";
las siguientes:
$mensaje .="<font face='Arial' size=6>Prueba HTML</font>";
multipart/alternative $mensaje .="</body></html>";
$responder="andres@mispruebas.com";
Es la forma de especificar que el $remite="andres@mispruebas.com";
mensaje tiene varias partes $remitente="Andrés Pérez y Pérez";
(multipart) de las que el
destinatario ha de ver una sola # creamos el separador de bloques del mensaje
(alternative). # anteponiento "_separador" aunque podríamos haber puesto "tiburcio"
Se podría utilizar en casos en los # generamos un identificador unico utilizando un numero aleatorio
que sea necesario prever la # como "semilla" y luego lo codificamos con la función md5
posibilidad de que un mensaje con
formato HTML pueda ser $separador ="_separador".md5 (uniqid (rand()));
visualizado como texto plano
cuando el cliente de correo no # creamos la variable cabecera con los elementos
soporte HTML. # ya utilizados en los ejemplos anteriores y ponemos al final
# de cada elemento UN SALTO DE LINEA
Podemos hacer un mensaje a
medida que se presentará de una
forma u otra según el cliente $cabecera = "Date: ".date("l j F Y, G:i").$UN_SALTO;
utilizado para leerlo. $cabecera .="MIME-Version: 1.0n";
$cabecera .="From: ".$remitente."<".$remite.">".$UN_SALTO;
multipart/mixed $cabecera .= "Return-path: ". $remite.$UN_SALTO;
$cabecera .="Cc:perico@mispruebas.com".$UN_SALTO;
Cuando en el Content-Type se
$cabecera .="Reply-To: ".$remite.$UN_SALTO;
establece el tipo multiparte y el
$cabecera .="X-Mailer: PHP/". phpversion().$UN_SALTO;
subtipo mezclado (mixed) será
cuando tengamos la posibilidad de
adjuntar ficheros al mensaje. # AQUÍ DEFINIMOS EL CONTENIDO MULTIPART, fíjate que lo acabamos con ";"
Las diferentes partes de un $cabecera .="Content-Type: multipart/alternative;".$UN_SALTO;
mensaje deben ir separadas –
tanto en modo alternativo como # insertamos BOUNDARY (fíjate que dejo un espacio
mezclado– y para ello hay que # en BLANCO DELANTE y ponemos al FINAL los DOS SALTOS DE LINEA
incluir un nuevo elemento en el
encabezado. Se trata de un
$cabecera .=" boundary=$separador".$DOS_SALTOS;
separador al que se llama
boundary.
# colocamos el primer separador(con los dos guiones delante)
boundary=cadena # antes de insertar la primera parte del mensaje
3. # que es el texto plano para el caso de que el cliente de correo
Dentro del encabezado y siempre # no soporte HTML
en línea aparte (fíjate que en los
ejemplos o está en línea aparte o
$texto_plano ="--$separador".$UN_SALTO;
aparece el n) debemos incluir el
elemento boundary= (sin símbolo
de $ delante) y detrás del signo # especificamos el tipo de contenido y la codificación
igual una cadena (en este caso # e inserto DOS SALTOS AL FINAL ya que ahi acaba la cabecera de esta parte
entre comillas) que en principio $texto_plano .="Content-Type:text/plain; charset="ISO-8859-1"".
puede ser una cadena cualquiera $UN_SALTO;
que no contenga espacios, aunque $texto_plano .="Content-Transfer-Encoding: 7bit".$DOS_SALTOS;
lo habitual es incluirla con el
formato que podemos ver en los # cambiamos las etiquetas "<br>" por saltos de línea
ejemplos. # y luego quitamos todas las etiquetas HTML del cuerpo del mensaje
# ya que el texto plano no debe llevar ese tipo de etiquetas
El cuerpo del mensaje
$extractor= strip_tags(eregi_replace("<br>", $UN_SALTO, $mensaje));
En su formato más simple el
cuerpo del mensaje contiene $texto_plano .=$extractor;
únicamente texto, pero cuando se
trata de multipartes deberá # insertamos un nuevo separador para señalar el final
contener necesariamente: los # de la primera parte del mensaje y el comienzo de la segunda
separadores de las diferentes # en este caso ponemos UN SALTO delante del separador ya que de lo
partes, los encabezados de cada contrario
una de las partes y sus respectivos # al componer el mensaje se uniría con la cadena texto_plano anterior
contenidos. # que no tiene SALTO DE LINEA AL FINAL
La secuencia habría de ser de este
tipo: $texto_html =$UN_SALTO."--$separador".$UN_SALTO;
# especificamos el encabezado HTML para el siguiente bloque
s Separador
s Content-type
# y ponemos en la ultima línea los DOS SALTOS DE LINEA
s Content-Transfer-Encoding
s **Content-Disposition $texto_html .="Content-Type:text/html; charset="ISO-8859-1"".$UN_SALTO;
s **Lectura del fichero $texto_html .="Content-Transfer-Encoding: 7bit".$DOS_SALTOS;
s **Codificación #añado la cadena que contiene el mensaje
s Inserción en cuerpo $texto_html .= $mensaje;
s Separador
s ..... # insertamos SOLAMENTE un SALTO DE LINEA
s otra parte # estamos al funal del mensaje
s ...
s Separador final
$texto_html .=$UN_SALTO;
Los apartados señalados con ** # unimos ambas cadenas para crear el cuerpo del mensaje
sólo se incluirían en el caso de que
junto con el mensaje se adjunten $mensaje=$texto_plano.$texto_html;
ficheros.
Content-type # enviamos el mensaje utilizando
Los tipos y subtipos más
habituales son los siguientes: if( mail($destinatario, $titulo, $mensaje,$cabecera)){
echo "mensaje enviado ";
Para incluir textos:
}
los ya mencionados
text/plain
text/html ?>
Para imágenes:
según el tipo de imagen ejemplo100.php
image/jpeg
image/gif
Para sonidos: Mensaje con ficheros adjuntos
audio/basic
Para vídeo:
video/mpeg
<?
Para ejecutables, comprimidos y # definimos estas variables igual que en el ejemplo anterior
otros ficheros adjuntos:
application/octet-stream $UN_SALTO="rn";
En cualquier caso, si quieres $DOS_SALTOS="rnrn";
utilizar algún otro tipo de archivo
puedes consultar en la web las #incluimos en varias, asunto, un texto en HTML
especificaciones del MIME. # remitente, etc. etc.
Aparte de tipo/subtipo puede $destinatario="perico@mispruebas.com";
añadirse a Content-type -en el $titulo="Mensaje con dos fichero adjuntos";
caso de texto- separado por punto $mensaje="<html><head></head><body bgcolor="#ff0000">";
y coma, la especificación del tipo $mensaje .="<font face="Arial" size=6>Prueba HTML </font>";
de alfabeto (charset=) seguida
$mensaje .="</body></html>";
del tipo de codificación (te
sugerimos el "ISO-8859-1" que
$responder="andres@mispruebas.com";
hace alusión al alfabeto latino). $remite="andres@mispruebas.com";
$remitente="Andrés otra vez";
Cuando se trata de ficheros
adjuntos deberemos poner, # definimos el separador de parte
después del punto y coma, # con el mismo procedimiento del ejemplo anterior
name= seguido del nombre y
extensión del fichero que se $separador = "_separador_de_trozos_".md5 (uniqid (rand()));
adjunta.
Content-Transfer-Encoding # insertamos los datos de la cabecera del mensaje
Este apartado del encabezado $cabecera = "Date: ".date("l j F Y, G:i").$UN_SALTO;
puede especificar una de los $cabecera .= "MIME-Version: 1.0".$UN_SALTO;
siguientes codificaciones: $cabecera .= "From: ".$remitente."<".$remite.">".$UN_SALTO;
4. 7BIT $cabecera .= "Return-path: ". $remite.$UN_SALTO;
8BIT $cabecera .= "Reply-To: ".$remite.$UN_SALTO;
BASE64
$cabecera .="X-Mailer: PHP/". phpversion().$UN_SALTO;
BINARY
QUOTED-PRINTABLE
# especificamos el tipo de contenido mutipart/mixed
La transferencia codificada en 7bit # ya que ahora insertaremos ficheros de distinto tipo
representa la codificación habitual
en el formato ASCII de 7 bits. $cabecera .= "Content-Type: multipart/mixed;".$UN_SALTO;
No permite caracteres ASCII con
un código mayor que 127. # insertamos el valor de boundary haciéndola igual a $separador
# y acabamos con DOS SALTOS porque es el FINAL DE LA CABECERA
Quoted-printable constituye una
de las alternativas al formato
ASCII de 7 bits. $cabecera .= " boundary=$separador".$DOS_SALTOS;
Esta codificación suele usarse /* Parte primera del envio -Mensaje en formato HTML
cuando la mayoría de los ================================================
caracteres del mensaje puede
escribirse con formato US ASCII de Separador inicial
7 bits. ------------------------------- */
Prevé que los caracteres con $texto ="--$separador".$UN_SALTO;
códigos ASCII superiores 127 se
expresen mediante un mecanismo /* Encabezado parcial
especial. ------------------ */
/* especificamos que este primer elemento
Evita, entre otras cosas, que las será texto y que irá codificado en formato 7 bits */
letras con tilde y algunos otros
caracteres especiales se visualicen
$texto .="Content-Type: text/html; charset="ISO-8859-1"".$UN_SALTO;
incorrectamente. Es la forma de
$texto .="Content-Transfer-Encoding: 7bit".$DOS_SALTOS;
codificación más recomendable
para textos.
/* Contenido de esta parte del mensaje
La codificación en base64 -----------------------------------*/
convierte cadenas binarias en # ya teniamos escrito el texto del mensaje más arriba
cadenas de texto, con lo cual # simplemente lo añadimos a la cadena de texto
pueden ser enviadas de forma más
segura. Es la forma de codificación $texto .= $mensaje;
habitual de las imágenes y los
ficheros exe, zip, etcétera.
#la variable $texto recoge esta parte del documento
Content-Disposition # la uniremos al final con las siguientes
Se utiliza únicamente cuando se /* Separador de partes
insertan ficheros adjuntos. -------------------- */
Permite dos opciones: inline o
attachment.
$adj1 = $UN_SALTO."--$separador".$UN_SALTO;
Inline permite que los contenidos /* Parte segunda de mensaje -Fichero adjunto nº 1
se visualicen junto con el cuerpo ==================================================== */
del mensaje mientras que con
attachment sólo aparecerían /* Encabezado parcial
como ficheros adjuntos. ------------------ */
# especificamos el tipo de contenido image/jpeg
Por lo que hemos podido
comprobar Outlook Express no # ya que ese será el documento que vamos a enviar
suele respetar esa condición y # ponemos el nombre del fichero (debemos tenerlo en el servidor
presenta siempre las imágenes en # con ese mismo nombre)
el mensaje. Sin embargo, sí # establecemos in line como disposición para que pueda ser visualizado
funciona en los correos web. # directamente en el cuerpo del mensajes
# en filename le asignamos el nombre con el que queremos que sea
Este elemento del encabezado # recibido por el destinatario
lleva -separada por punto y coma-
# por ultimo especificamos la codificacion como base64
una segunda parte.
El filename=, donde se puede $adj1 .="Content-Type: image/jpeg;";
especificar entre comillas un $adj1 .=" name="casa08.jpg"".$UN_SALTO;
nombre y una extensión (igual o $adj1 .="Content-Disposition: inline; ";
distinta de la original) con la que $adj1 .="filename="leoncio.jpg"".$UN_SALTO;
se denominará al fichero en el $adj1 .="Content-Transfer-Encoding: base64".$DOS_SALTOS;
mensaje recibido.
/* Lectura previa del fichero a adjuntar
Lectura del fichero
------------------------------------------ */
Cuando se trata de insertar un # abrimos el fichero en modo lectura (r)
fichero el proceso es el típico de # y leemos todo su contenido midiendo previamente
lectura de ficheros, es decir: # su longitud con filesize
# recogemos en $buff el contenido del fichero
s Hay que crear el # y cerramos después
identificador de recurso del
fichero en modo lectura. $fp = fopen("casa08.jpg", "r");
s Recoger en una variable el $buff = fread($fp, filesize("casa08.jpg"));
buffer de lectura. fclose($fp);
s Cerrar el fichero.
/* Codificación del fichero a adjuntar
Codificación ------------------------------------------ */
# codificamos en base 64 y troceamos en lineas de 76 caracteres
Una vez recogido en el fichero a # y añadimos el resultado a la variable adj1
transmitir en una variable, el paso
siguiente es codificar esa variable.
$adj1 .=chunk_split(base64_encode($buff));
Utilizaremos la codificación más
habitual y flexible –base64– que /* Separador de partes
requerirá el uso de dos nuevas -------------------- */
funciones PHP:
$adj2 = $UN_SALTO."--$separador".$UN_SALTO;
base64_encode
chunk_split
/* Tercera parte de mensaje -Fichero adjunto nº 2
Mediante la primera se realiza la ==================================================== */
5. codificación propiamente dicha
mientras que la segunda organiza /* Encabezado parcial
el fichero codificado en trozos, de
------------------ */
76 caracteres cada uno, insertando
# los contenidos del encabezado son similares al caso anterior
detrás de cada uno un salto de
línea. # con la salvedad de que el contenido es ahora
# application/octet-stream ya que contiene un fichero ejecutable
Si analizas un mensaje de correo # y la disposicion es attachment, no tiene sentido tratar
que contenga un fichero adjunto – # de visualizar un fichero zip
propiedades, ver codificación–,
podrás ver esa fragmentación tan $adj2 .="Content-Type: application/octet-stream;";
cuidada -un montón de líneas de $adj2 .=" name="apachito.zip"".$UN_SALTO;
texto muy raro- perfectamente
$adj2 .="Content-Disposition: attachment;
alineadas por los márgenes por
filename="apachito.zip"".$UN_SALTO;
efecto de chunk_split.
$adj2 .="Content-Transfer-Encoding: base64".$DOS_SALTOS;
Inserción en el cuerpo
/* Lectura previa del fichero a adjuntar
La fase final del proceso es la de ------------------------------------------ */
agrupar los diferentes trozos en # abrimos el fichero en modo lectura (r)
una sola variable, que será la que # y leemos todo su contenido midiendo previamente
se insertará como parámetro texto
# su longitud con filesize
en la función e-mail.
# recogemos en $buff el contenido del fichero
¡Cuidado! # y cerramos después
La inserción de ficheros adjuntos $fp = fopen("apachito.zip", "r");
requiere que éstos estén $buff = fread($fp, filesize("apachito.zip"));
disponibles en el servidor por lo fclose($fp);
que, antes de enviarlos, habrá que
subirlos al servidor utilizando un
/* Codificación del fichero a adjuntar
proceso como el que hemos
analizado cuando hablábamos de ------------------------------------------ */
Transferencia de ficheros.
$adj2 .=chunk_split(base64_encode($buff));
Sobre los ejemplos /* Separador final YA NO HAY MAS PARTES
---------------------------------------- */
Hemos incluido dos ejemplos
relativos al envío de ficheros en $adj2 .=$UN_SALTO."--$separador".$UN_SALTO;
formato HTML y con ficheros
adjuntos. /* Unión de todas las PARTES
No entraremos en el estudio ---------------------------------------- */
detallado del MIME. # unimos en la variable mensaje todos los elementos
# y lo hacemos por el orden en el que fueron creados
Quedaremos únicamente en sus
aspectos funcionales en cuanto a $mensaje=$texto.$adj1.$adj2;
los requerimientos de formato,
separadores, etc. /* Envio del mensaje
Quizá te parezca obsesivo el ---------------------------------------- */
hincapié que hacemos en los
ejemplos sobre los detalles de la if(mail($destinatario, $titulo, $mensaje,$cabecera)){
sintaxis. echo "mensaje enviado";
}
Nuestra insistencia se debe al
carácter sumamente estricto de la ?>
especificación MIME, donde un
simple salto de línea puede ser la
causa de que script deje de ejemplo101.php
funcionar.
Ejercicio nº 32
Diseña un script de forma que al cargarse la página que lo contiene se envíe –de forma
automática– un mensaje de correo, indicando la fecha y hora de acceso, al usuario
juan@mispruebas.com
Anterior Indice Siguiente