Talán sokaknak okozott már gondod a PHP kicsit fapados, viszont ennél fogva nagyon jól testre szabható mail() függvénye. Senki se szereti az ide vonatkozó RFC-ket túrni azért, hogy tudjon küldeni egy HTML levelet, vagy hogy egy képet tudjon csatolni a kiküldött levélhez.

A továbbiakban megnézzük, hogyan tudunk felépíteni egy HTML levelet, amihez egy képet csatoltunk hozzá. A dolog ott bonyolódik meg, hogy úgy szeretnénk azt a HTML levelet kiküldeni, hogy akinek az e-mail olvasója nem támogatja az ilyen leveleket az is el tudja majd olvasni. Azaz alternatívaként a levélben meg kell adnunk a sima szöveg változatot is.

A küldendő levél felépítése
A bal oldalamon látható, hogy hogyan fog felépülni az e-mail maga. A dolog lényege, hogy valami egyedi karaktersorozatot tartalmazó elválasztó egységekkel több részre bontjuk a levél tartalmát, és ezeknek a részeknek a típusát és azt, hogy az e-mail olvasó hogy kezelje őket, külön megadhatjuk. Tehát a levél kezdetben így fog kinézni:

--boundary_mixed
ide jön majd a levél szövege

--boundary_mixed
ide jön majd a kép

--boundary_mixed--

A "boundary_mixed" az egyedi azonosító, amivel daraboljuk a levelet. Új blokk kezdésénél két kötőjelet írunk az elválasztó elé, ha pedig le akarjuk zárni az utolsó blokkot, akkor az elválasztó elé és mögé is írunk két kötőjelet. A dolog ott bonyolódik csak meg egy picit, hogy a levél szövegét is több részre kell bontani majd, tehát egy lépéssel később a levél tartalma már így fog kinézni:

--boundary_mixed
Content-Type: multipart/alternative; boundary=boundary_alt

--boundary_alt
a levél sima szövege

--boundary_alt
a levél html szövege

--boundary_alt--

--boundary_mixed
ide jön majd a kép

--boundary_mixed--

Mint látszik, az első blokk elején megadtuk a blokk tartalmának a típusát (ezt majd a többi blokk kezdetnél is meg kell majd adnunk), amiben definiáltunk egy új elválasztó elemet. A multipart/alternative típus tulajdonsága, hogy a benne felsorolt blokkokból azt jeleníti meg, amelyiket a felhasználó e-mail olvasója támogat. Épp ezt akartuk elérni, hogy ha a kliens nem támogatja a HTML alapú leveleket, akkor a sima szöveget kapja meg. A levél tartalma így fog kinézni, miután mindenhol megadtuk a megfelelő fejléc elemeket:

--boundary_mixed
Content-Type: multipart/alternative; boundary=boundary_alt

--boundary_alt
Content-Type: text/plain; charset=iso-8859-2
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

a levél sima szövege

--boundary_alt
Content-Type: text/html; charset=iso-8859-2
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

a levél html szövege

--boundary_alt--

--boundary_mixed
Content-Type: image/gif; name=image.gif
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=image.gif

a kép base64-el elkódolva

--boundary_mixed--

A multipart résztől eltekintve a Content-Type fejléc teljesen hasonlóan viselkedik, mint a HTML-es fejlécek. A Content-Transfer-Encoding határozza meg, hogy milyen formátumban van az adott szöveg abban a blokkban. Kép esetén ez egy base64-es elkódolást jelent, szöveg esetén a quoted-printable értéket használjuk, aminek az esetén a blokk az ASCII karakterkészlet nyomtatható karaktereiből álló karakterláncot fog tartalmazni. A Content-Disposition azt adja meg, hogy a blokk a levél megjelenítésekor hogyan legyen kezelve (csatolt állományként (attachment), vagy jelenjen meg az e-mailben (inline)). A levél tartalma már megvan, most már csak a levél fejlécét kell megadnunk, ami annyiban módosul, hogy ott kell definiálnunk az első elválasztó elemünket (boundary_mixed) és meg kell adnunk a MIME verzióját:

From: a levél feladója
MIME-Version: 1.0
Content-type: multipart/mixed; boundary=boundary_mixed

Nincs más hátra, mint hogy ezt az egészet PHP-ben is megvalósítsuk, ami innentől már nem lesz egy bonyolult dolog:

$files   = array('image/gif' => 'image.gif');
$from    = 'a levél feladója';
$to      = 'a levél címzettje';
$subject = 'a levél témája';
$content = 'a levél tartalma, ami HTML-t is tartalmazhat';

$bd_mixed = md5(uniqid(rand(), true));
$bd_alt   = md5(uniqid(rand(), true));

$header =
'From: '.$from.'
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="'.$bd_mixed. '" ';

$text =
'--'.$bd_mixed. '
Content-Type: multipart/alternative; boundary="'.$bd_alt.'"

--'.$bd_alt.'
Content-Type: text/plain; charset=iso-8859-2
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

'.trim(strip_tags($content)).'

--'.$bd_alt.'
Content-Type: text/html; charset=iso-8859-2
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

'.$content.'

--'.$bd_alt.'--

';

foreach ($files as $mime => $name) {
	$text .=
'--'.$bd_mixed.'
Content-Type: '.$mime.'; name="'.$name.'"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="'.$name.'"

'.chunk_split(base64_encode(file_get_contents($name))).'

';
}
$text .= '--'.$bd_mixed.'--';

mail($to, $subject, $text, $header);

Ennyi lenne a kód, mint említettem, nem egy bonyolult dolog, a fentebb írt mail-darabolási ismeret birtokában. Talán csak a chunk_split függvény szorul némi magyarázatra: ha a szövegen kívül nem adunk meg neki más paramétert, akkor a 2045-ös RFC-nek megfelelő szemantika alapján 76 karakterenként tesz egy "\r\n"-t a szövegbe.

Erről ennyit, kellemes csatolt fájlokkal tűzdelt e-mail küldözgetést mindenkinek. :)