トップへ(mam-mam.net/)

How to Send HTML Emails in PHP|Understanding Multipart Structure and Embedded Images

Japanese

Sending HTML Emails with Attachments Using a Multipart Structure

When sending HTML emails in PHP, there are many practical elements to consider, such as multipart structures for text and HTML, file attachments, and Cc/Bcc settings.
You can copy and paste the code on this page and use it as-is.
This guide explains how HTML email delivery works using practical, UTF‑8‑compatible PHP code.

To send an HTML email with attachments in PHP, you need a multipart structure that combines text and HTML, along with proper boundary handling.
Since mb_send_mail() has limitations, you must manually construct a multipart/mixed message.
This page provides working code based on RFC‑compliant methods.

Preparing mamsendmail.php

Prepare the mamsendmail.php file located at the bottom of this page.

Sending the Email

<?php
include(__DIR__.'/mamsendmail.php');
mb_language("Japanese");
mb_internal_encoding("UTF-8");  //Send email in UTF-8
//From
$from="mam <from@mam-mam.net>";
//Recipients
//  At least one address must be specified in To or Cc (sending with only Bcc may fail)
//  Multiple recipients can be separated by commas (,)
$to= "To Recipient 1 <to1@mam-mam.net>, To Recipient 2 <to2@mam-mam.net>";
$cc= "<cc1@mam-mam.net>, Cc Recipient 2<cc2@mam-mam.net>, cc3@mam-mam.net";
$bcc="Bcc Recipient 1 <bcc1@mam-mam.net>, bcc2@mam-mam.net, <bcc3@mam-mam.net>";
//Subject
$subject="Subject: You can also include long subjects. Half-width characters are allowed. 23784sdarew!#$%&()=-~^|";
//Plain text body (set to empty string if not needed);
$textBody="Title\r\n  Body\r\n  Test email";
//HTML body (set to empty string if not needed)
$htmlBody="
  <html>
  <head>
    <style>
      h3{color:red;font-size:32px;font-weight:bold;}
      p{color:black;font-size:20px;}
      img{width:100%;max-width:500px;}
    </style>
  </head>
  <body>
    <h3>Title</h3>
    <p>
      This is the message body.<br>
      <img src=\"cid:img1\"><br>
      <img src=\"cid:img2\"><br>
      <img src=\"cid:img3\"><br>
      This is the message body.
    </p>
  </body>
  </html>";
//Attachments (set to [] if not needed)
$files=[__DIR__."/../imgs/0001s.jpg", __DIR__."/../imgs/0002s.jpg", __DIR__."/../imgs/0003s.jpg"];

//Send the email
mamSendMail::send(
  $from,
  $to,$cc,$bcc,
  $subject,
  $textBody,  $htmlBody,
  $files
); 

mamsendmail.php

<?php
mb_language("Japanese");
mb_internal_encoding("UTF-8");  //Send email in UTF-8

/*
  mamSendMail::send(arguments) — UTF‑8 email sending class
  Meaning of each argument:
    $from(required)
      Specify exactly one email address.
      例1: $from="from@mam-mam.net";
      例2: $from="<from@mam-mam.net>";
      例3: $from="Saito <from@mam-mam.net>";
    $to,$cc,$bcc
      At least one recipient must be included in either $to or $cc
      (sending with only Bcc may fail depending on the server).
      Multiple addresses can be separated with commas (,).
      Example:
        $to="Mr. Sato <to1@mam-mam.net>, Mr. Yamada <to2@mam-mam.net>, to3@mam-mam.net, <to4@mam-mam.net>";
      If cc or bcc is not needed, set them to an empty string:  $cc="";$bcc="";
    $subject
      Email subject line.
    $textBody
      Plain‑text email body.
      例: $textBody="Test\r\n  Body\r\n";
    $htmlBody
      HTML email body.
      Example: $htmlBody="<html><body><h1 style='font-size:20px;'>Test</h1><p>Body</p></body></html>";
    Notes:
      • If both $textBody and $htmlBody contain content:
            -> The email is sent as a multipart message (text + HTML).
      • If $textBody is empty and $htmlBody contains content:
            -> The email is sent as an HTML‑only message.
      • If $textBody contains content and $htmlBody is empty:
            -> The email is sent as a plain‑text message.
    $files
      If needed, specify attachment file paths in an array.
      Example:
        $files = ["C:/temp/1.jpg", "C:/temp/2.png", "C:/temp/3.png"];
      If not needed, specify an empty array or omit the argument:
        $files = [];
*/
class mamSendMail{
  public static function send(
    string $from,
    string $to, string $cc, string $bcc,
    string $subject,
    string $textBody, string $htmlBody,
    array $files=[]
  ){
    //Validate attachment files
    for($i=0;$i<count($files);$i++){
      if(!file_exists($files[$i])){
        unset($files[$i]);
        $files=array_values($files);
      }
    }

    $fFrom=self::mamExplodeMailAddress($from);
    if(count($fFrom)==0){return false;}
    if(empty($fFrom[0]["name"])){
      $cFrom=$fFrom[0]["add"];
    }else{
      $cFrom=mb_encode_mimeheader($fFrom[0]["name"],"UTF-8","B","\r\n",10).$fFrom[0]["add"];
    }

    $fTo=self::mamExplodeMailAddress($to);
    //if(count($fTo)==0){return false;}
    $cTo="";
    for($i=0;$i<count($fTo);$i++){
      if(!empty($cTo)){$cTo.=",";}
      if(empty($fTo[$i]["name"])){
        $cTo.=$fTo[$i]["add"];
      }else{
        $cTo.=mb_encode_mimeheader($fTo[$i]["name"],"UTF-8").$fTo[$i]["add"];
      }
    }
    $fCc=self::mamExplodeMailAddress($cc);
    $cCc="";
    for($i=0;$i<count($fCc);$i++){
      if(!empty($cCc)){$cCc.=",";}
      if(empty($fCc[$i]["name"])){
        $cCc.=$fCc[$i]["add"];
      }else{
        $cCc.=mb_encode_mimeheader($fCc[$i]["name"],"UTF-8").$fCc[$i]["add"];
      }
    }
    $fBcc=self::mamExplodeMailAddress($bcc);
    $cBcc="";
    for($i=0;$i<count($fBcc);$i++){
      if(!empty($cBcc)){$cBcc.=",";}
      if(empty($fBcc[$i]["name"])){
        $cBcc.=$fBcc[$i]["add"];
      }else{
        $cBcc.=mb_encode_mimeheader($fBcc[$i]["name"],"UTF-8").$fBcc[$i]["add"];
      }
    }

    $cSubject=mb_encode_mimeheader($subject);

    $cHeaders=[
      "From"=>$cFrom,
      "Reply-To"=>$cFrom,
      "Content-Transfer-Encoding"=>"base64",
      "X-Mailer"=>"PHP Mailer",
    ];

    if(!empty($textBody)&&!empty($htmlBody)){
      //When Text+HTML email
      $fHtmlBody=chunk_split(base64_encode($htmlBody));
      $fTextBody=chunk_split(base64_encode($textBody));
      $fBoundary="b1".uniqid("",true);

      if(count($files)==0){
        //When there are no attachments
        $cHeaders["Content-Type"]='multipart/alternative; boundary="'.$fBoundary.'"';
        $cBody=
          "--".$fBoundary."\r\n".
          "Content-Type: text/plain; charset=UTF-8\r\n".
          "Content-Disposition: inline\r\n".
          "Content-Transfer-Encoding: base64\r\n".
          "\r\n".
          $fTextBody.
          "\r\n".
          "--".$fBoundary."\r\n".
          "Content-Type: text/html; charset=UTF-8\r\n".
          "Content-Disposition: inline\r\n".
          "Content-Transfer-Encoding: base64\r\n".
          "\r\n".
          $fHtmlBody.
          "\r\n".
          "--".$fBoundary."--\r\n";
      }else{
        //When there are attachments
        $fBoundary2="b2".uniqid("",true);
        $cHeaders["Content-Type"]='multipart/mixed; boundary="'.$fBoundary.'"';
        $cBody=
          "--".$fBoundary."\r\n".
          "Content-Type: multipart/alternative; boundary=\"".$fBoundary2."\"\r\n".
          "\r\n".
          "--".$fBoundary2."\r\n".
          "Content-Type: text/plain; charset=UTF-8\r\n".
          "Content-Disposition: inline\r\n".
          "Content-Transfer-Encoding: base64\r\n".
          "\r\n".
          $fTextBody.
          "\r\n".
          "--".$fBoundary2."\r\n".
          "Content-Type: text/html; charset=UTF-8\r\n".
          "Content-Disposition: inline\r\n".
          "Content-Transfer-Encoding: base64\r\n".
          "\r\n".
          $fHtmlBody.
          "\r\n".
          "--".$fBoundary2."--\r\n".
          "\r\n".
          "--".$fBoundary."\r\n";
        for($i=0;$i<count($files);$i++){
          if(preg_match('/\.gif$/i',$files[$i])){
            $hd="image/gif";
            $cd="inline";
          }else if(preg_match('/\.jpg$|\.jpeg$/i',$files[$i])){
            $hd="image/jpeg";
            $cd="inline";
          }else if(preg_match('/\.png$/i',$files[$i])){
            $hd="image/png";
            $cd="inline";
          }else{
            $hd="application/octet-stream";
            $cd="attachment";
          }
          $cBody.=
            "Content-Type: ".$hd."; name=\"".basename($files[$i])."\"\r\n".
            "Content-Disposition: ".$cd."; filename=\"".basename($files[$i])."\"\r\n".
            "Content-Transfer-Encoding: base64\r\n".
            "Content-ID: <img".($i+1).">\r\n".
            "Content-Location: ".basename($files[$i])."\r\n".
            "\r\n".
            chunk_split(base64_encode(file_get_contents($files[$i])));
          if($i==(count($files)-1)){
            $cBody.="--".$fBoundary."--\r\n";
          }else{
            $cBody.="--".$fBoundary."\r\n";
          }
        }
      }
    }else if(!empty($htmlBody)){
      //Only HTML email
      if(count($files)==0){
        $cHeaders["Content-Type"]='text/html;charset=utf-8';
        $cBody=chunk_split(base64_encode($htmlBody));
      }else{
        $fBoundary="b1".uniqid("",true);
        $cHeaders["Content-Type"]='multipart/mixed; boundary="'.$fBoundary.'"';
        $fHtmlBody=chunk_split(base64_encode($htmlBody));
        $cBody=
          "--".$fBoundary."\r\n".
          "Content-Type: text/html; charset=UTF-8\r\n".
          "Content-Disposition: inline\r\n".
          "Content-Transfer-Encoding: base64\r\n".
          "\r\n".
          $fHtmlBody."\r\n".
          "--".$fBoundary."\r\n";
        for($i=0;$i<count($files);$i++){
          if(preg_match('/\.gif$/i',$files[$i])){
            $hd="image/gif";
            $cd="inline";
          }else if(preg_match('/\.jpg$|\.jpeg$/i',$files[$i])){
            $hd="image/jpeg";
            $cd="inline";
          }else if(preg_match('/\.png$/i',$files[$i])){
            $hd="image/png";
            $cd="inline";
          }else{
            $hd="application/octet-stream";
            $cd="attachment";
          }
          $cBody.=
            "Content-Type: ".$hd."; name=\"".basename($files[$i])."\"\r\n".
            "Content-Disposition: ".$cd."; filename=\"".basename($files[$i])."\"\r\n".
            "Content-Transfer-Encoding: base64\r\n".
            "Content-ID: <img".($i+1).">\r\n".
            "Content-Location: ".basename($files[$i])."\r\n".
            "\r\n".
            chunk_split(base64_encode(file_get_contents($files[$i])));
          if($i==(count($files)-1)){
            $cBody.="--".$fBoundary."--\r\n";
          }else{
            $cBody.="--".$fBoundary."\r\n";
          }
        }
      }
    }else{
      //Only Text email
      if(count($files)==0){
        $cHeaders["Content-Type"]='text/plain;charset=utf-8';
        $cBody=chunk_split(base64_encode($textBody));
      }else{
        $fBoundary="b1".uniqid("",true);
        $cHeaders["Content-Type"]='multipart/mixed; boundary="'.$fBoundary.'"';
        $fTextBody=chunk_split(base64_encode($textBody));
        $cBody=
          "--".$fBoundary."\r\n".
          "Content-Type: text/plain; charset=UTF-8\r\n".
          "Content-Disposition: inline\r\n".
          "Content-Transfer-Encoding: base64\r\n".
          "\r\n".
          $fTextBody."\r\n".
          "--".$fBoundary."\r\n";
        for($i=0;$i<count($files);$i++){
          if(preg_match('/\.gif$/i',$files[$i])){
            $hd="image/gif";
            $cd="inline";
          }else if(preg_match('/\.jpg$|\.jpeg$/i',$files[$i])){
            $hd="image/jpeg";
            $cd="inline";
          }else if(preg_match('/\.png$/i',$files[$i])){
            $hd="image/png";
            $cd="inline";
          }else{
            $hd="application/octet-stream";
            $cd="attachment";
          }
          $cBody.=
            "Content-Type: ".$hd."; name=\"".basename($files[$i])."\"\r\n".
            "Content-Disposition: ".$cd."; filename=\"".basename($files[$i])."\"\r\n".
            "Content-Transfer-Encoding: base64\r\n".
            "Content-ID: <img".($i+1).">\r\n".
            "Content-Location: ".basename($files[$i])."\r\n".
            "\r\n".
            chunk_split(base64_encode(file_get_contents($files[$i])));
          if($i==(count($files)-1)){
            $cBody.="--".$fBoundary."--\r\n";
          }else{
            $cBody.="--".$fBoundary."\r\n";
          }
        }
      }
    }

    if(!empty($cCc)){
      $cHeaders["Cc"]=$cCc;
    }
    if(!empty($cBcc)){
      $cHeaders["Bcc"]=$cBcc;
    }
    mail($cTo,$cSubject,$cBody,$cHeaders);
  }

  private static function mamExplodeMailAddress(string $add){
    $add=trim($add);
    if(empty($add)){return [];}
    $addresses=explode(",",$add);
    $ret=[];
    foreach($addresses as $address){
      $mail=["name"=>"", "add"=>$address];
      $s=mb_strpos($address,"<",0,"UTF-8");
      if($s!==false && $s>0){
        $e=mb_strpos($address,">",$s,"UTF-8");
        if($e!==false && $e>0){
          $mail["name"]=trim(mb_substr($address,0,$s));
          $mail["add"] =mb_substr($address,$s,$e);
        }
      }
      $ret[]=$mail;
    }
    return $ret;
  }
}

PHP | Samples