Introduzione
In questo articolo vedremo come sfruttare PHP per inviare e-mail in formato HTML. So già che qualcuno obbietterà che non è necessario utilizzare l'HTML nella comunicazione via posta elettronica, che il puro testo è sufficiente oltre che più "leggero" e più sicuro. Questo va bene per noi programmatori, ma il resto del modo apprezza messaggi colorati, magari composti da qualche immagine o suono. Allora utilizziamo al meglio gli strumenti messi a disposizione da PHP per confezionare messaggi vivaci e ben formattati.
Partiamo dal presupposto che il file php.ini sia correttamente configurato nella sezione [mail function] per l'invio di messaggi di posta elettronica. Per maggiori dettagli si rimanda all'articolo La La funzione mail() o alla documentazione ufficiale.
Lo strumento principale che utilizzeremo sarà la funzione mail(). Tale funzione prevede cinque argomenti tre dei quali obbligatori: l'indirizzo o gli indirizzi dei destinatari (basterà separarli con la virgola), l'oggetto del messaggio, il testo del messaggio.
mail("destinatario@dominio.it","oggetto del messaggio","testo del messaggio");
?>
Dei due argomenti facoltativi prenderemo in considerazione solo il primo, cioè quello che ci permette di specificare le intestazioni o headers del messaggio.
Facciamo un po' di chiarezza in proposito: ogni e-mail è sostanzialmente costituita da due sezioni, le cosiddette intestazioni corpo
Alcune intestazioni sono standardizzate (To, From, CC, Bcc, ecc.) altre invece non lo sono ed iniziano per X-, come X-Mailer, che ci permette di indicare il programma utilizzato per l'invio del messaggio. Quando inviamo un'e-mail con il nostro programma di posta elettronica preferito questi si occupa di generare le corrette intestazioni. Altre intestazioni corrispondono ai mail server coinvolti nella comunicazione. Il codice seguente esemplifica, invece, come ottenere con PHP un messaggio con le intestazioni sopra descritte:
Invio di un'e-mail in formato HTML
Tra le intestazioni che si possono specificare ve ne sono alcune fondamentali per raggiungere il nostro obiettivo. Tra queste i MIME, Multiporpouse Internet Mail Extensions. Come si intuisce dal nome, sono estensioni del formato originario con cui venivano inviati i messaggi di posta elettronica, introdotte per poter, ad esempio, inviare allegati o costruire e-mail in formato HTML corredate da immagini, suoni, applet ecc.
- La prima intestazione da utilizzare è MIME-Version
- La seconda, Content-Type
- La terza è Content-Transfer-Encoding
Mettiamo in pratica quanto teorizzato per costruire il nostro primo messaggio in formato HTML:
// costruiamo alcune intestazioni generali
$header = "From: Inviante <inviante@dominio.org>n";
$header .= "CC: Altro Ricevente <altroricevente@dominio.net>n";
$header .= "X-Mailer: Il nostro Phpn";
// costruiamo le intestazioni specifiche per il formato HTML
$header .= "MIME-Version: 1.0n";
$header .= "Content-Type: text/html; charset="iso-8859-1"n";
$header .= "Content-Transfer-Encoding: 7bitnn";
//costruiamo il testo in formato HTML
$messaggio = "<html><body><p>Questo messaggio è in formato <i>html</i></p><p>Visita il sito <a href="http://www.html.it">www.html.it</a></p></body></html>";
$subject = "primo messaggio html";
// inviamo il messaggio di posta elettronica
// controllando eventuali errori
if( @mail("ricevente@dominio.it", $subject, $messaggio, $header) ) echo "e-mail inviata con successo!";
else echo "errore nell'invio dell'e-mail!";
?>
Fin qui tutto bene, ma cosa succede se il client di posta del destinatario è stato configurato per non visualizzare il formato HTML
Per ottenere questo dovremo utilizzare un Content-Type multipart/alternative Boundary
La medesima stringa deve essere usata come terminatore dell'e-mail ed in questo caso, oltre ad essere preceduta dai due trattini, deve essere terminata con i due trattini "--". Potrebbe andare bene qualcosa di simile a MiaStringa123, ma, per evitare errori, conviene generarla in modo pseudocasuale. Ad esempio si può sfruttare la funzione md5(), che calcola l'hash md5 di una stringa, applicandola al timestamp unix così:
$boundary = "==String_Boundary_x" .md5(time()). "x";
Vediamo il codice che ci permette di ottenere quanto sopra descritto:
<?php
// costruiamo alcune intestazioni generali
$header = "From: Inviante <inviante@dominio.org>n";
$header .= "CC: Altro Ricevente <altroricevente@dominio.net>n";
$header .= "X-Mailer: Il nostro Phpn";
// generiamo la stringa che funge da separatore
$boundary = "==String_Boundary_x" .md5(time()). "x";
// costruiamo le intestazioni che specificano
// un messaggio costituito da più parti alternative
$header .= "MIME-Version: 1.0n";
$header .= "Content-Type: multipart/alternative;n";
$header .= " boundary="$boundary";nn";
// questa parte del messaggio viene visualizzata
// solo se il programma non sa interpretare
// i MIME poiché è posta prima della stringa boundary
$messaggio = "Se visualizzi questo testo il tuo programma non supporta i MIMEnn";
// inizia la prima parte del messaggio in testo puro
$messaggio .= "--$boundaryn";
$messaggio .= "Content-Type: text/plain; charset="iso-8859-1"n";
$messaggio .= "Content-Transfer-Encoding: 7bitnn";
$messaggio .= "Messaggio in formato testo.nn";
// inizia la seconda parte del messaggio in formato html
$messaggio .= "--$boundaryn";
$messaggio .= "Content-Type: text/html; charset="iso-8859-1"n";
$messaggio .= "Content-Transfer-Encoding: 7bitnn";
$messaggio .= "<html><body><p>Questo messaggio è in formato <i>html</i> ma ha una parte testo.</p><p>Visita il sito <a href="http://www.html.it">www.html.it</a></p></body></html>n";
// chiusura del messaggio con la stringa boundary
$subject = "secondo messaggio html";
if( @mail("ricevente@dominio.it", $subject, $messaggio, $header) ) echo "e-mail inviata con successo!";
else echo "errore nell'invio dell'e-mail!";
?>
Una piccola notazione: se vogliamo suddividere un header su più righe, le righe addizionali devono iniziare con uno o più spazi per indicare che sono la continuazione delle righe precedenti. Ecco perché il codice sopra riportato è stato scritto così:
Arricchiamo la posta elettronica con immagini
Dopo aver visto come sia possibile garantire la compatibilità con i client che non supportano l'HTML, concentriamoci sul formato HTML. Se vogliamo arricchire il nostro messaggio possiamo, ovviamente, rendere più complessa la struttura dell'e-mail, introducendo ad esempio degli stili, come faremmo nella progettazione di una qualsiasi pagina web. Come possiamo, invece, inserire immagini, suoni ed altri elementi che rendano più attraente il contenuto della comunicazione?
Una prima soluzione, semplificata, consiste nell'utilizzare dei riferimenti assoluti agli oggetti che vogliamo visualizzare nel messaggio. Supponiamo che sul nostro server, raggiungibile alla url http://www.mioserver.it, sia presente, nella directory images, il file miaimmagine.jpg che vogliamo visualizzare insieme al testo dell'e-mail. Possiamo procedere esattamente come sopra nella costruzione del messaggio modificandolo semplicemente così:
<img src="http://www.mioserver.it/images/miaimmagine.jpg">Questa soluzione poggia però su alcuni presupposti. Innanzi tutto il programma di posta elettronica di chi riceve l'e-mail deve essere in grado di accedere via protocollo http alle risorse che referenziamo mediante url. In secondo luogo il ricevente deve essere online mentre consulta il messaggio, altrimenti non visualizzerà le immagini o gli altri oggetti referenziati. Se vogliamo evitare che queste circostanze vanifichino i nostri sforzi creativi, dobbiamo utilizzare degli oggetti "inclusi" nel messaggio o, come si dice, inline.
È giunto il momento di introdurre un altro header MIME il Content-ID
Anche in questo caso dobbiamo ottenere un messaggio costituito da più parti: il testo in formato HTML e l'immagine, quindi sarà "multipart". Le diverse parti non sono tra loro alternative, come nell'esempio precedente, bensì in relazione quindi "related".
<?php
// costruiamo alcune intestazioni generali
$header = "From: Inviante <inviante@dominio.org>n";
$header .= "CC: Altro Ricevente <altroricevente@dominio.net>n";
$header .= "X-Mailer: Il nostro Phpn";
// generiamo la stringa che funge da separatore
$boundary = "==String_Boundary_x" .md5(time()). "x";
// costruiamo le intestazioni specifiche per un messaggio
// con parti relazionate
$header .= "MIME-Version: 1.0n";
$header .= "Content-Type: multipart/related;n";
$header .= " boundary="$boundary";nn";
// questa parte del messaggio viene visualizzata
// solo se il programma non sa interpretare
// i MIME poiché è posta prima della stringa boundary
$messaggio = "Se visualizzi questo testo il tuo programma non supporta i MIMEnn";
// costruiamo la sezione in formato html
$messaggio .= "--$boundaryn";
$messaggio .= "Content-Type: text/html; charset="iso-8859-1"n";
$messaggio .= "Content-Transfer-Encoding: 7bitnn";
$messaggio .= "<html><body><p>Questo messaggio è in formato <i>html</i> con immagine inline.</p><p>Visita il sito <a href="http://www.html.it">www.html.it</a><img src="cid:MiaImmagine123" alt="mia immagine"></p></body></html>nn";
// costruiamo la sezione contenente l'immagine
// cui attribuiamo l'id MiaImmagine123
$messaggio .= "--$boundaryn";
$messaggio .= "Content-ID: <MiaImmagine123>n";
$messaggio .= "Content-Type: image/jpegn";
$messaggio .= "Content-Transfer-Encoding: base64nn";
// leggiamo il file corrispondente all'immagine dal nostro server
$allegato = "./images/miaimmagine.jpg";
$file = fopen($allegato,'rb');
$data = fread($file,filesize($allegato));
fclose($file);
// usiamo la codifica base64 per trasmettere il file
$data = chunk_split(base64_encode($data));
$messaggio .= "$datann";
// chiusura del messaggio con la stringa boundary
$messaggio .= "--$boundary--n";
$subject = "messaggio con immagine inline";
if( @mail("ricevente@dominio.it", $subject, $messaggio, $header) ) echo "e-mail inviata con successo!";
else echo "errore nell'invio dell'e-mail!";
?>
È necessario soffermarsi brevemente a commentare alcune parti del codice. Innanzi tutto notiamo che nella sezione HTML del messaggio non troviamo più un riferimento assoluto all'immagine, bensì un riferimento all'identificatore della porzione in cui l'immagine stessa è stata codificata, preceduto da "cid:".
<img src="cid:MiaImmagine123" alt="mia immagine">
Corrispondentemente la sezione relativa all'immagine presenta un header Content-ID Content-Type
La codifica di trasmissione, ovvero l'header Content-Transfer-Encoding
Il messaggio che abbiamo generato sarà sicuramente più "pesante" di quello che utilizza le url per referenziare gli oggetti. Consideriamo che il destinatario potrebbe avere una connessione lenta: non dobbiamo bloccargli la posta costringendolo a tempi di download eccessivi.
Prevediamo ancora un'alternativa testuale
Con un ultimo sforzo mettiamo insieme quanto abbiamo sin qui visto per realizzare un messaggio HTML con un'immagine "embedded", ma che presenti anche l'alternativa in testo puro. Dovremo definire un messaggio multipart/related, ma una delle due parti dovrà essere a sua volta un multipart/alternative per prevedere sia il testo puro che il formato HTML.
Per realizzare tutto questo dovremo ricorrere a due stringhe boundary: una farà da separatore del multipart/related e l'altra del multipart/alternative.
// costruiamo alcune intestazioni generali
$header = "From: Inviante <inviante@dominio.org>n";
$header .= "CC: Altro Ricevente <altroricevente@dominio.net>n";
$header .= "X-Mailer: Il nostro Phpn";
// generiamo le stringhe utilizzate come separatori
$boundary = "==String_Boundary_x" .md5(time()). "x";
$boundary2 = "==String_Boundary2_y" .md5(time()). "y";
// costruiamo le intestazioni specifiche per un messaggio
// con parti relazionate
$header .= "MIME-Version: 1.0n";
$header .= "Content-Type: multipart/related;n";
$header .= " type="multipart/alternative";n";
$header .= " boundary="$boundary";nn";
$messaggio = "Se visualizzi questo testo il tuo programma non supporta i MIMEnn";
// il primo segmento del multipart/related
// è definito come multipart/alternative
$messaggio .= "--$boundaryn";
$messaggio .= "Content-Type: multipart/alternative;n";
$messaggio .= " boundary="$boundary2";nn";
// sezione alternativa in puro testo
$messaggio .= "--$boundary2n";
$messaggio .= "Content-Type: text/plain; charset="iso-8859-1"n";
$messaggio .= "Content-Transfer-Encoding: 7bitnn";
$messaggio .= "Messaggio alternativo in formato testo.nn";
// sezione alternativa in formato html
$messaggio .= "--$boundary2n";
$messaggio .= "Content-Type: text/html; charset="iso-8859-1"n";
$messaggio .= "Content-Transfer-Encoding: 7bitnn";
$messaggio .= "<html><body><p>Questo messaggio è in formato <i>html</i> ma ha una parte testo.</p><p>Visita il sito <a href="http://www.html.it">www.html.it</a><img src="cid:MiaImmagine123"></p></body></html>n";
// chiusura della sezione multipart/alternative
$messaggio .= "--$boundary2--n";
// seconda sezione del multipart/related contenente l'immagine
$messaggio .= "--$boundaryn";
$messaggio .= "Content-ID: <MiaImmagine123>n";
$messaggio .= "Content-Type: image/jpegn";
$messaggio .= "Content-Transfer-Encoding: base64nn";
$allegato = "./images/miaimmagine.jpg";
$file = fopen($allegato,'rb');
$data = fread($file,filesize($allegato));
fclose($file);
$data = chunk_split(base64_encode($data));
$messaggio .= "$datann";
// chiusura della sezione multipart/related
$subject = "oggetto del messaggio alternativo con immagini inline";
if( @mail("ricevente@dominio.it", $subject, $messaggio, $header) ) echo "e-mail inviata con successo!";
else echo "errore nell'invio dell'e-mail!";
?>
Conclusioni
In questo articolo abbiamo esplorato le possibilità fornite dai MIME per generare messaggi in formato HTML, naturalmente non abbiamo esaurito tutte le potenzialità di tali estensioni. Per chi fosse interessato, ad esempio, alla generazione di messaggi con file allegati rimando all'articolo già citato La funzione mail().
Si noti che il codice sopra riportato è volutamente semplificato, data la sua natura esemplificativa, per questo non adatto ad essere utilizzato "in produzione" con un semplice copia incolla. Si riferisce specificamente ad un sistema unix-like con web server Apache. Sono documentate in Rete alcune anomalie di comportamento relative alla funzione PHP mail() su server Windows.
Se decidete di lavorare con le estensioni MIME sappiate che non tutti i client di posta elettronica le interpretano esattamente allo stesso modo, specialmente nei casi più complessi in cui si utilizzino varie sezioni multipart annidate. Questo significa che l'implementazione del codice richiederà pazienza, diversi esperimenti e si dovrà procedere per successivi aggiustamenti. Facendo riferimento a Microsoft Outlook per Windows (il client di posta elettronica probabilmente più diffuso nel nostro paese) ho rilevato in alcune circostanze comportamenti difformi addirittura tra la versione principale e la versione "light" Outlook Express. D'altro canto chi ha un po' di esperienza sa che, purtroppo, la standardizzazione rimane una chimera.
Gli esempi riportati sono comunque frutto di una ricerca di compatibilità tra i client di posta e le webmail più utilizzate.