PHPの基礎 - メール

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
ナビゲーションに移動 検索に移動

概要

mail関数またはmb_send_mail関数を使用して、メールを送信する手順を記載する。


php.iniの設定

mail関数またはmb_send_mail関数では、php.iniファイルで設定したSMTPサーバ名とポート番号の値を読み込んで使用する。
そのため、php.iniファイルの該当箇所を適切な値に設定する必要がある。

php.iniファイルの[mail function]において、以下の設定を記述する。

  • SMTP
    SMTPサーバのホスト名またはIPアドレスを指定する。
    もし、ローカル環境のメール送信用サーバを使用する場合、localhostでもよい。
  • smtp_port
    メールの送信時に使用するポート番号を指定する。
    標準のポート番号は25番であるが、迷惑メール対策でOutbound Port25 Blockingが実施されている場合は、587番等を使用する。
    使用するメールサーバに合わせて設定すること。
  • sendmail_from
    Windowsで使用する場合、sendmail_fromにメール送信元のメールアドレスを設定できる。
    設定する場合は、先頭のセミコロン(;)を外してメールアドレスを設定する。
[mail function]
; For Windows only.
SMTP = <SMTPサーバのホスト名またはIPアドレス>
smtp_port = <ポート番号>

; For Windows only.
;sendmail_from = me@example.com

; For Unix only.  You may supply arguments as well (default: "sendmail -t -i").
;sendmail_path =

; Force the addition of the specified parameters to be passed as extra parameters
; to the sendmail binary. These parameters will always replace the value of
; the 5th parameter to mail(), even in safe mode.
;mail.force_extra_parameters =


以下に、設定例を示す。

[mail function]
; For Win32 only.
SMTP = smtp.example.jp
smtp_port = 25

; For Win32 only.
sendmail_from = peke@example.jp


設定変更後は、Apache等のWebサーバを再起動する。


ASCIIメールの送信 : mail関数

ASCIIメールの送信を行うには、mail関数を使用する。

bool mail ( string to, 
            string subject, 
            string message 
            [, string additional_headers 
            [, string additional_parameters]] )


パラメータ:
   to  メールの宛先
   subject  メールのタイトル
   message  メールの本文
   additional_headers  追加ヘッダ
   additional_parameters  追加パラメータ

戻り値:
   メール送信が受け入れられた場合はTRUE、それ以外の場合はFALSE


メールの宛先、タイトル、本文を指定してメールを送信する。

以下に、メールの宛先(メールアドレス)の書式を示す。
ただし、Windowsで使用する場合は、User <user@example.com>の形式は使用できない。

user@example.com
user@example.com, anotheruser@example.com
User <user@example.com>
User <user@example.com>, Another User <anotheruser@example.com>


メールのタイトルに指定する文字列は、改行できない。

メールの本文は、複数行の文字列を指定できる。
改行する場合は¥n(LF)を使用する。
なお、各行の長さは70文字を超えてはいけないため、wordwrap関数を使用して70文字以上含まれる行は改行する。

 $message = wordwrap($message, 70, "¥n");


追加ヘッダは、From、Cc、Reply-To等のヘッダとして、記述する内容を指定する。
複数のヘッダを追加する場合は、¥r¥n(CRLF)で区切る。
ヘッダの中で、Fromヘッダのみ必須となっている。
引数のadditional_headersは省略可能であるが、php.iniファイルでsendmail_fromを設定していない場合は、Fromヘッダの記述が必要である。

追加パラメータは、sendmail等へパラメータを渡す場合に使用する。
ただし、Windowsの場合は使用しない。

例えば、以下のような記述となる。

 $to = 'you@example.com';
 $subject = 'Test Title';
 $message = "This is Test mail¥nMulti Line";
 $message = wordwrap($message, 70, "¥n");
 $headers = 'From: my@example.com';
 
 mail($to, $subject, $message, $headers);


以下の例において、index.phpというファイル名で、/<Webサーバのドキュメントルート>/mailディレクトリに保存する。
送信元および送信先のメールアドレスに変更して、実際に使用できるメールアドレスに変更する。

次に、Webブラウザを起動して、以下のWebサイトにアクセスする。
http://localhost/mail/index.php

SMTPサーバがメールの送信を受け入れた場合、Webブラウザに成功と表示される。

 <!DOCTYPE html>
 <html lang="">
 <head>
    <meta charset="utf-8">
    <title>PHP TEST</title>
 </head>
 <body>
    <h1>メール送信</h1>
    <?php
       $to = 'you@example.com';
       $subject = 'test mail';
       $message = "This is Test mail¥nMulti Line";
       $message = wordwrap($message, 70, "¥n");
       $headers = 'From: my@example.com'."¥r¥n".
                  'To: you@example.com'."¥r¥n".
                  'X-Mailer: PHP/Mail';
 
       if(mail($to, $subject, $message, $headers))
       {
          print('成功');
       }
       else
       {
          print('エラー');
       }
    ?>
 </body>
 </html>



IMAP

PHP標準のIMAPモジュール (ビルトインのIMAPモジュール)

IMAPプロトコルは、1980年代に開発された比較的古いプロトコルである。
しかし、現在でも多くのメールサーバでサポートされている。

PHP標準のIMAPモジュール (ビルトイン済み) は、PHPの公式拡張であるが長年更新が少なく、レガシーな実装が残っている。
このモジュールはPHPのc-clientライブラリに依存しているため、システムレベルでのインストールが必要となる。

PHP標準のIMAPモジュールの特徴として、基本的なIMAP操作のみであり、エラーハンドリングが古い方式、日本語等のエンコーディング処理が複雑である。

 // PHP標準のIMAPモジュール
 
 $inbox  = imap_open("{imap.example.com:993/imap/ssl}INBOX", "<ユーザ名>", "<パスワード>");
 $emails = imap_search($inbox, 'ALL');


IMAPモジュールの代替手段の充実しており、Composer経由で利用できる現代的なメールライブラリが多数存在する。
以下に示すライブラリは、PHPのビルトインIMAPよりも使用しやすく、メンテナンスも活発である。

  • php-imap/php-imap
  • Webklex/php-imap
  • ddeboer/imap


しかし、PHPでは、IMAPモジュールの必要性は以前と比べて低くなっている。
現在では、以下に示す理由からIMAPを使用しないほうがよい。

  • セキュリティ面での考慮が必要になる。
  • 依存関係の管理が複雑になる。
  • システムレベルでの追加ライブラリのインストールが必要である。
  • クロスプラットフォームでの一貫性の問題がある。


IMAPの代替

代替可能なものとして、以下に示すようなものが存在する。

  • REST APIを使用したメールサービスの利用
  • Gmail API
  • Microsoft Graph API (Office 365/Exchange)
  • メールサービスプロバイダのSDK活用
  • キューイングシステムとの連携


したがって、以下に示すような場合を除き、IMAPモジュールは使用しないことを推奨する。

  • レガシーシステムとの互換性が必要な場合
  • 直接IMAPサーバーとの通信が絶対に必要な場合
  • 特定のIMAPプロトコル機能に依存している場合


php-imap/php-imapライブラリ (Composer)

IMAPを使わざるを得ない場合の最善の選択肢として、php-imap/php-imapを使用することを推奨する。

php-imap/php-imapライブラリは、現代的なPHPの機能を活用した積極的にメンテナンスされているライブラリである。
Composerでインストール可能であり、PHPのビルトインIMAPモジュールをラップする形で実装されている。

php-imap/php-imapの特徴として、以下に示すものが挙げられる。

  • 現代的な例外処理
  • より直感的なAPI
  • 添付ファイル処理の改善
  • エンコーディング処理の自動化
  • タイプヒントのサポート
  • より良いドキュメント


 <?php
 // Composerで使用されるPHPのパッケージ管理システムに関連する重要な記述
 // 必要なクラスファイルを自動的にrequire/includeする
 require 'vendor/autoload.php';
 
 use PhpImap\Mailbox;
 use PhpImap\Exceptions\ConnectionException;
 use PhpImap\Exceptions\InvalidParameterException;
 
 /*
  * メール処理を行うクラス
  */
 class MailProcessor
 {
    private $mailbox;
    private $server;
    private $username;
    private $password;
 
    /**
     * コンストラクタ
     * 
     * @param string $server IMAPサーバーアドレス (例: imap.example.com)
     * @param string $username ユーザー名
     * @param string $password パスワード
     */
    public function __construct(string $server, string $username, string $password)
    {
       $this->server = $server;
       $this->username = $username;
       $this->password = $password;
    }
 
    /**
     * メールボックスへの接続を確立
     * 
     * @throws ConnectionException 接続エラー時
     * @return bool 接続成功時にtrue
     */
    public function connect(): bool
    {
       try {
          // IMAPサーバへの接続文字列を構築
          $imapPath = sprintf('{%s:993/imap/ssl}INBOX', $this->server);
 
          // メールボックスオブジェクトを初期化
          $this->mailbox = new Mailbox($imapPath, $this->username, $this->password,
                                       __DIR__ . '/attachments', // 添付ファイル保存ディレクトリ
                                       'UTF-8'                   // 文字エンコーディング
          );
 
          // 接続
          $this->mailbox->checkMailbox();
          return true;
       }
       catch (ConnectionException $e) {
          throw new ConnectionException('メールサーバーへの接続に失敗しました: ' . $e->getMessage());
       }
    }
 
    /**
     * メールを検索して取得
     * 
     * @param string $criteria 検索条件 (例: 'ALL', 'UNSEEN', 'FROM "someone@example.com"')
     * @return array 検索結果のメール情報配列
     */
    public function searchMails(string $criteria = 'ALL'): array
    {
       try {
          // メールを検索
          $mailsIds = $this->mailbox->searchMailbox($criteria);
          $results = [];
 
          foreach ($mailsIds as $mailId) {
             try {
                $email = $this->mailbox->getMail($mailId);
                $results[] = [
                        'id' => $mailId,
                        'subject' => $email->subject,
                        'from' => $email->fromAddress,
                        'date' => $email->date,
                        'body' => $email->textPlain,
                        'hasAttachments' => $email->hasAttachments()
                ];
             }
             catch (\Exception $e) {
                // 個別のメール取得エラーをログに記録し、処理を継続
                error_log("メールID {$mailId} の取得に失敗: " . $e->getMessage());
                continue;
             }
          }
          return $results;
       }
       catch (InvalidParameterException $e) {
          throw new InvalidParameterException('無効な検索条件が指定されました: ' . $e->getMessage());
       }
    }
 
    /**
     * メールボックスの接続を終了
     */
    public function disconnect(): void
    {
       if ($this->mailbox) $this->mailbox->disconnect();
    }
 }


 // 使用例
 
 try {
    // メールプロセッサのインスタンスを生成
    $processor = new MailProcessor(
        'imap.example.com',
        '<ユーザー名>',
        '<パスワード>'
    );
 
    // 接続
    $processor->connect();
 
    // 未読メールを検索
    $unreadMails = $processor->searchMails('UNSEEN');
 
    // 結果を処理
    foreach ($unreadMails as $mail) {
       echo "Subject: " . $mail['subject'] . "\n";
       echo "From: " . $mail['from'] . "\n";
       echo "Date: " . $mail['date'] . "\n";
    }
 }
 catch (ConnectionException $e) {
    // 接続エラーの処理
    error_log("接続エラー: " . $e->getMessage());
    exit(1);
 }
 catch (InvalidParameterException $e) {
    // パラメータエラーの処理
    error_log("パラメータエラー: " . $e->getMessage());
    exit(1);
 }
 catch (\Exception $e) {
    // その他の予期せぬエラーの処理
    error_log("予期せぬエラー: " . $e->getMessage());
    exit(1);
 }
 finally {
    // 確実に接続を終了
    if (isset($processor)) {
        $processor->disconnect();
    }
 }


推奨されるアプローチ

新規システムを設計する場合は、以下に示す順序で検討することが推奨される。

  1. クラウドメールサービスのAPIを使用
    • Gmail API
    • Microsoft Graph API
    • その他のモダンなメールサービスAPI

  2. メールサービスプロバイダのAPIが使用できない場合
    • SMTP/IMAPの代わりにRESTful APIを提供する中間サービス
      例: Mailgun、SendGrid、Amazon SES等

  3. IMAPを使わざるを得ない場合
    • php-imap/php-imapを使用
      より現代的なライブラリとして保守されているため


上記の理由から、可能であれば、よりモダンなAPIベースのソリューションを検討することが推奨される。


APIベースのソリューション

Google Gmail API

Composerで必要なライブラリをインストールする。

composer require google/apiclient


 // クライアントの初期化
 $client = new Google_Client();
 $client->setApplicationName('Your Application Name');
 $client->setScopes([Google_Service_Gmail::GMAIL_READONLY]);
 $client->setAuthConfig('path/to/credentials.json'); // Google Cloud Consoleから取得した認証情報
 
 // OAuth2認証フロー
 if (!isset($_SESSION['access_token'])) {
    // 認証URLの生成
    $authUrl = $client->createAuthUrl();
    header('Location: ' . $authUrl);
 
    exit;
 }
 else {
    $client->setAccessToken($_SESSION['access_token']);
 
    // トークンの有効期限が切れている場合は更新
    if ($client->isAccessTokenExpired()) {
       $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
       $_SESSION['access_token'] = $client->getAccessToken();
    }
 }
 
 // Gmail APIの利用
 $gmail = new Google_Service_Gmail($client);
 
 // メール一覧の取得
 $optParams = [
    'maxResults' => 10,      // 取得する件数
    'labelIds' => ['INBOX']  // INBOXのメールを取得
 ];
 
 $messagesList = $gmail->users_messages->listUsersMessages('me', $optParams);
 
 foreach ($messagesList->getMessages() as $message) {
    // 個別のメッセージIDを取得
    $messageId = $message->getId();
 
    // 個別のメッセージの詳細を取得
    $message = $gmail->users_messages->get('me', $messageId);
 
    // メッセージの内容を取得
    $headers = $messageDetail->getPayload()->getHeaders();
 
    // 件名、送信者等の情報を取得
    $subject = '';
    $from = '';
    foreach ($headers as $header) {
       if ($header->getName() == 'Subject') {
          $subject = $header->getValue();
       }
 
       if ($header->getName() == 'From') {
          $from = $header->getValue();
       }
    }
 
    echo "ID: {$messageId}\n";
    echo "Subject: {$subject}\n";
    echo "From: {$from}\n";
 }


  • 検索条件を指定してメッセージを取得する場合
 $optParams = [
    'q' => 'from:example@gmail.com',  // 検索クエリ
    'maxResults' => 5
 ];
 
 // 昨日以降のメール
 $optParams = ['q' => 'after:' . date('Y/m/d', strtotime('-1 day'))];
 
 // 未読メール
 $optParams = ['q' => 'is:unread'];
 
 // 特定の件名のメール
 $optParams = ['q' => 'subject:"Meeting Invitation"'];
 
 // 添付ファイルがあるメール
 $optParams = ['q' => 'has:attachment'];


  • メッセージの詳細情報の取得例
 function getEmailContent($gmail, $messageId)
 {
    $message = $gmail->users_messages->get('me', $messageId, ['format' => 'full']);
    $payload = $message->getPayload();
 
    // メール本文の取得
    $body = '';
    if ($payload->getBody()->getData()) {
       $body = base64url_decode($payload->getBody()->getData());
    }
    else {
       // マルチパートの場合
       $parts = $payload->getParts();
       foreach ($parts as $part) {
          if ($part->getMimeType() === 'text/plain') {
             $body = base64url_decode($part->getBody()->getData());
             break;
          }
       }
    }
 
    return [
       'id' => $messageId,
       'threadId' => $message->getThreadId(),
       'labelIds' => $message->getLabelIds(),
       'body' => $body,
       // その他必要な情報
    ];
 }
 
 // base64url_decode関数の定義
 function base64url_decode($data)
 {
    return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
 }


※注意
メールを取得する場合には、以下に示す事柄に注意すること。

  • レート制限に注意する。
  • 必要な権限(スコープ)が設定されていることを確認する。
  • 大量のメールを処理する場合はページネーションを使用する。


Microsoft Graph API

Composerで必要なライブラリをインストールする。

composer require microsoft/microsoft-graph


 // アプリケーションの登録情報
 $clientId     = 'YOUR_CLIENT_ID';
 $clientSecret = 'YOUR_CLIENT_SECRET';
 $tenantId     = 'YOUR_TENANT_ID';
 $redirectUri  = 'YOUR_REDIRECT_URI';
 
 // OAuth2認証の設定
 $provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => $clientId,
    'clientSecret'            => $clientSecret,
    'redirectUri'             => $redirectUri,
    'urlAuthorize'            => "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/authorize",
    'urlAccessToken'          => "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token",
    'urlResourceOwnerDetails' => '',
    'scopes'                  => 'Mail.Read'
 ]);
 
 // アクセストークンの取得
 if (!isset($_SESSION['access_token'])) {
    // 認証コードの取得
    if (!isset($_GET['code'])) {
       $authUrl = $provider->getAuthorizationUrl();
       header('Location: ' . $authUrl);
 
       exit;
    }
 
    // 認証コードを使用してアクセストークンを取得
    $accessToken = $provider->getAccessToken('authorization_code', [
       'code' => $_GET['code']
    ]);
    $_SESSION['access_token'] = $accessToken->getToken();
 } 
 
 $accessToken = $_SESSION['access_token'];
 
 // Graph APIの利用
 $graph = new Graph();
 $graph->setAccessToken($accessToken);
 try {
    $messages = $graph->createRequest("GET", "/me/messages")->execute();
 }
 catch (\Microsoft\Graph\Exception\GraphException $e) {
    // エラー処理
    error_log('Graph API Error: ' . $e->getMessage());
 }