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.
<?php
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 ed il corpo vero e proprio del messaggio. Tali sezioni per essere distinguibili sono separate da una linea vuota. Ogni intestazione è caratterizzata da un nome seguito da un valore. Ecco un esempio di alcune possibili intestazioni:
To: Ricevente <ricevente@dominio.it>
From: Inviante <inviante@dominio.org>
CC: Altro Ricevente <altroricevente@dominio.net>
Bcc: Ricevente Nascosto <nascosto@dominio.com>
X-Mailer: Il nostro Php
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:
<?php
$header = "To: Ricevente <ricevente@dominio.it>n";
$header .= "From: Inviante <inviante@dominio.org>n";
$header .= "CC: Altro Ricevente <altroricevente@dominio.net>n";
$header .= "Bcc: Ricevente Nascosto <nascosto@dominio.net>n";
$header .= "X-Mailer: Il nostro Phpnn";
$oggetto = "oggetto del messaggio";
$messaggio = "testo del messaggio vero e proprio";
mail("ricevente@dominio.it",$oggetto,$messaggio,$header);
?>
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 che permette di specificare la versione dello standard MIME.
- La seconda, Content-Type, specifica il contenuto del messaggio che potrebbe essere testo puro (text/plain), o HTML (text/html), o un'immagine GIF (image/gif) o altro ancora.
- La terza è Content-Transfer-Encoding ovvero la codifica con cui viene trasmesso il contenuto.
Mettiamo in pratica quanto teorizzato per costruire il nostro primo messaggio in formato HTML:
<?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";
// 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, oppure non ne prevede la visualizzazione come accade per alcune webmail? Per evitare tale inconveniente sarà necessario predisporre due versioni del messaggio, una testo puro ed una HTML, lasciando al programma la scelta di visualizzare quella che ritenga più adatta.
Per ottenere questo dovremo utilizzare un Content-Type multipart/alternative per indicare che il messaggio è costituito da più parti ("multipart") le quali sono tra loro alternative ("alternative"). Per permettere al client di posta di distinguere l'una dall'altra le due parti del messaggio è necessario ricorrere ad un separatore. Tale separatore consiste in una stringa arbitraria chiamata Boundary. Requisito fondamentale è che la stringa sia unica all'interno del messaggio in modo da non confondersi con porzioni del testo. Essa deve inoltre iniziare con due trattini "--".
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
$messaggio .= "--$boundary--n";
$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ì:
$header .= "Content-Type: multipart/alternative;n";
$header .= " boundary="$boundary";nn";
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ì:
$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="http://www.mioserver.it/images/miaimmagine.jpg"></p></body></html>n";
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. Questa intestazione permette di associare un identificatore univoco a ciascuna sezione del messaggio, nel nostro caso particolare all'immagine che vogliamo visualizzare e che includeremo nel corpo dell'e-mail in modo simile ad un allegato. Tramite questo suo identificatore l'immagine potrà poi essere richiamata all'interno della sezione HTML del messaggio.
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 cui è stato attribuito il valore MiaImmagine123. Ottimizzando il codice sarebbe bene generare l'id dell'oggetto incluso in modo analogo a quanto fatto per la stringa boundary. Il Content-Type sarà image/jpeg trattandosi di un file in tale formato.
La codifica di trasmissione, ovvero l'header Content-Transfer-Encoding, è stata impostata a base64. Questo è un sistema di codifica molto diffuso che permette di trasformare dati binari in un formato "testuale" adatto alla trasmissione via e-mail. Per nostra fortuna PHP ci facilita il lavoro fornendo la funzione base64_encode(), inoltre la funzione chunk_split() ci permette di suddividere i dati in porzioni di 76 caratteri inserendo rn, come richiesto dalla codifica di trasmissione.
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.
<?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 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
$messaggio .= "--$boundary--n";
$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.