在下再嘗試使用 fsockopen 發送電子郵件
使用 PHP 下,一般都會使用內建的 mail() 功能
需要修改 php.ini 的 SMTP 資料,修改後還要重新啟動伺服器才能應用
若需要測試不同的 SMTP 伺服器,經常這樣修改比較麻煩,利用 ini_set 可以暫時修改 php.ini
但有些情況是閣下並不是伺服器擁有者,不能隨意修改 php.ini 、 重新啟動伺服器 、 ini_set 的修改受限制
若使用 fsockopen 便可以直接使用像 Telnet 連接 SMTP 伺服器的指令來發送電子郵件
以 Gmail 作例子
Gmail 的 SMTP 位置為 smtp.gmail.com
由於 Gmail 需要使用安全通道加密資料,因此使用 fsockopen 時需要以 ssl:// 連接至 465 port 或以 tls:// 連接至 587 port
1 |
1 |
1 2 | $response = fgets ( $handle ); echo $response ; |
1 | 220 smtp.gmail.com ESMTP <b>ID< /b > - gsmtp |
留意:
回應內容,前端必定有一組 三位數 的 SMTP回應碼
另在由於 SMTP 完成整個發送程序前,都會以互動形容操作
若當前操作為使用者給予指令,但使用如 fgets 等獲取伺服器的回應,便會變成不斷等待伺服器
而判斷是否是使用者給予指令的狀態,可以以 第四字元 是否為 空白 作判斷
若伺服器的回應不只一行, SMTP回應碼 會以 - 減號連接
例如
連接到 Gmail 的 SMTP 後編寫
1 | fwrite( $handle , "EHLO gmail.smtp.com\r\n" ); |
如
1 2 3 4 5 6 7 8 | 250-smtp.gmail.com at your service, [<b>IP< /b >] 250-SIZE 35882577 250-8BITMIME 250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH 250-ENHANCEDSTATUSCODES 250-PIPELINING 250-CHUNKING 250 SMTPUTF8 |
1 2 3 4 5 6 7 | function check_response( $handle ) { $response = fgets ( $handle ); echo $response ; if ( substr ( $response , 3, 1) != ' ' ) { check_response( $handle ); } } |
完成 EHLO 後,編寫
1 2 | fwrite( $handle , "AUTH LOGIN\r\n" ); check_response( $handle ); |
1 | 334 VXNlcm5hbWU6 |
編寫
1 2 | fwrite( $handle , base64_encode ( $username ) . "\r\n" ); check_response( $handle ); |
1 | 334 UGFzc3dvcmQ6 |
編寫
1 2 | fwrite( $handle , base64_encode ( $password ) . "\r\n" ); check_response( $handle ); |
1 | 235 2.7.0 Accepted |
1 | 535 5.7.8 https: //support .google.com /mail/ ?p=BadCredentials <b>ID< /b > - gsmtp |
登入 SMTP 後,可以正式編寫郵件資料及內容
編寫
1 2 | fwrite( $handle , "MAIL FROM: <sender@gmail.com>\r\n" ); check_response( $handle ); |
1 | 250 2.1.0 OK <b>ID< /b > - gsmtp |
編寫
1 2 | fwrite( $handle , "RCPT TO: <recipient@gmail.com>\r\n" ); check_response( $handle ); |
1 | 250 2.1.0 OK <b>ID< /b > - gsmtp |
編寫
1 2 | fwrite( $handle , "DATA\r\n" ); check_response( $handle ); |
1 | 354 Go ahead <b>ID< /b > - gsmtp |
使用 DATA 後的內容資料可以當作遵從 HTTP Request 的寫法,例如
1 2 3 4 5 6 7 8 9 | $request = '' ; $request .= "header-1: data-1\r\n" ; $request .= "header-2: data-2\r\n" ; $request .= "header-3: data-3\r\n" ; $request .= "\r\n" ; $request .= "message body\r\n" ; $request .= ".\r\n" ; fwrite( $handle , $request ); check_response( $handle ); |
若需要加入附件,便需要在加入
1 | Content-Type: multipart /mixed ; boundary= "END_OF_PART" |
內容需要使用
1 | Content-Type: text /plain ; charset=UTF-8; format =flowed |
1 2 3 | Content-Type: <b>MIME Type< /b >; charset=UTF-8 Content-transfer-encoding: <b>Encryption Method< /b > Content-Disposition: attachment; filename= "filename.ext" |
例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | $request = '' ; $request .= "header-1: data-1\r\n" ; $request .= "header-2: data-2\r\n" ; $request .= "header-3: data-3\r\n" ; $request .= 'Content-Type: multipart/mixed; boundary="END_OF_PART"' . "\r\n" ; $request .= "MIME-Version: 1.0\r\n" ; $request .= "\r\n" ; $request .= "--END_OF_PART\r\n" ; $request .= "Content-Type: text/plain; charset=UTF-8; format=flowed\r\n" ; $request .= "\r\n" ; $request .= "message body\r\n" ; $request .= "--END_OF_PART\r\n" ; $request .= "Content-Type: image/jpeg\r\n" ; $request .= "Content-Transfer-Encoding: base64\r\n" ; $request .= 'Content-Disposition: attachment; filename="filename.jpg"' . "\r\n" ; $request .= "\r\n" ; $request .= base64_encode ( $file_contents ) . "\r\n" ; $request .= "--END_OF_PART\r\n" ; $request .= ".\r\n" ; fwrite( $handle , $request ); check_response( $handle ); |
傳回回應
1 | 250 2.0.0 OK <b>timestamp< /b > <b>ID< /b > - gsmtp |
1 | 503 5.5.1 MAIL first. <b>ID< /b > - gsmtp |
1 | 503 5.5.1 RCPT first. <b>ID< /b > - gsmtp |
最後完成後編寫
1 2 | fwrite( $handle , "QUIT\r\n" ); check_response( $handle ); |
1 | 221 2.0.0 closing connection <b>ID< /b > - gsmtp |
以下為測試程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | function check_response( $handle , $code ) { $response = fgets ( $handle ); echo $response ; if ( substr ( $response , 3, 1) != ' ' ) { check_response( $handle , $code ); } else { if ( substr ( $response , 0, 3) != $code ) { die (); } } } function send_request( $handle , $request , $code ) { echo $request ; fwrite( $handle , $request ); check_response( $handle , $code ); } $host = 'smtp.gmail.com' ; $errno = 0; $error = '' ; check_response( $handle , 220); send_request( $handle , 'EHLO ' . $host . "\r\n" , 250); send_request( $handle , "AUTH LOGIN\r\n" , 334); echo "***** Hidden Request *****\r\n" ; fwrite( $handle , base64_encode ( $username ) . "\r\n" ); check_response( $handle , 334); echo "***** Hidden Request *****\r\n" ; fwrite( $handle , base64_encode ( $password ) . "\r\n" ); check_response( $handle , 235); send_request( $handle , 'MAIL FROM: <' . $sender_email . ">\r\n" , 250); send_request( $handle , 'RCPT TO: <' . $recipient_email . ">\r\n" , 250); send_request( $handle , "DATA\n" , 354); $request = '' ; $request .= "Subject: This is subject\r\n" ; $request .= 'From: "' . $sender_name . '" <' . $sender_email . ">\r\n" ; $request .= 'To: "' . $recipient_name . '" <' . $recipient_email . ">\r\n" ; $request .= 'Content-Type: multipart/mixed; boundary="END_OF_PART"' . "\r\n" ; $request .= "MIME-Version: 1.0\r\n" ; $request .= "\r\n" ; $request .= "--END_OF_PART\r\n" ; $request .= "Content-Type: text/plain; charset=UTF-8; format=flowed\r\n" ; $request .= "\r\n" ; $request .= "This is body\r\n" ; $request .= "--END_OF_PART\r\n" ; $request .= "Content-Type: text/plain; charset=UTF-8\r\n" ; $request .= "Content-Transfer-Encoding: base64\r\n" ; $request .= 'Content-Disposition: attachment; filename="filename.txt"' . "\r\n" ; $request .= "\r\n" ; $request .= base64_encode ( 'This is attachment' ) . "\r\n" ; $request .= "--END_OF_PART--\r\n" ; $request .= ".\r\n" ; send_request( $handle , $request , 250); send_request( $handle , "QUIT\r\n" , 221); } else { printf( '[%d] %s' , $errno , $error ); } |
得到如下的指令及回應
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 220 smtp.gmail.com ESMTP <b>***** Hidden ID *****< /b > - gsmtp EHLO smtp.gmail.com 250-smtp.gmail.com at your service, [<b>***** Hidden IP *****< /b >] 250-SIZE 35882577 250-8BITMIME 250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH 250-ENHANCEDSTATUSCODES 250-PIPELINING 250-CHUNKING 250 SMTPUTF8 AUTH LOGIN 334 VXNlcm5hbWU6 <b>***** Hidden base64 encoded Username *****< /b > 334 UGFzc3dvcmQ6 <b>***** Hidden base64 encoded Password *****< /b > 235 2.7.0 Accepted MAIL FROM: <<b>***** Hidden Sender E-Mail *****< /b >> 250 2.1.0 OK <b>***** Hidden ID *****< /b > - gsmtp RCPT TO: <<b>***** Hidden Recipient E-Mail *****< /b >> 250 2.1.5 OK <b>***** Hidden ID *****< /b > - gsmtp DATA 354 Go ahead <b>***** Hidden ID *****< /b > - gsmtp Subject: This is subject From: "Mr.Z" <<b>***** Hidden Sender E-Mail *****< /b >> To: "Mr.A" <<b>***** Hidden Recipient E-Mail *****< /b >> Content-Type: multipart /mixed ; boundary= "END_OF_PART" MIME-Version: 1.0 --END_OF_PART Content-Type: text /plain ; charset=UTF-8; format =flowed This is body --END_OF_PART Content-Type: text /plain ; charset=UTF-8 Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename= "filename.txt" VGhpcyBpcyBhdHRhY2htZW50 --END_OF_PART-- . 250 2.0.0 OK 1505711793 <b>***** Hidden ID *****< /b > - gsmtp QUIT 221 2.0.0 closing connection <b>***** Hidden ID *****< /b > - gsmtp |
參考結果



在送出 DATA 指令後的標頭資料,例如 From 及 To 不一定要與 MAIL FROM 及 RCPT TO 相對
基本上這些標頭資料都不一定要加上,或胡亂編寫都可以
這些資料是為了方便收件人了解郵件資料,例如 To (收件), Cc (副本), Bcc (密件) 等
關於更多 SMTP 標頭資料可以到 IETF 的 RFC 4021 文件 查看
實際上 PHPMailer 都是使用 fsockopen 的方式發送電子郵件
沒有留言 :
張貼留言