在php中使用SMTP通过密抄批量发送邮件

这几天,在整一个php在线邮件批量发送的程序来着。如果是一人一封一封发送的话,耗时又久又资源,抄送的话,收件人就会看到其他收件人了,这种时候,密抄就最是适合了。

什么是抄送、密抄?

抄送就是将一封邮件同时发给多个收件人,各收件人都可以看到其他收件人地址。抄送效果和平时发邮件时,用“,”将地址隔开的差不多
密抄和抄送差不多,但是,各收件人都不可以看到其他收件人地址,换句话说,他收到这封邮件,根本没法知道这个是群发还是单独发送。只有发件人自己知道。

 

密抄的实现就是在邮件头里面,用Bcc加入邮件地址就可以了,如

Bcc: xin478<xin478@126.com>,xin478<44522303@qq.com>

这种情况下,该邮件就会附带发送到126和qq这两个邮箱上,但是谁都看不出来这封邮件还发给了谁。

 

因为是多用户系统,所以要求就是用户可以自己设置发送者的邮箱地址,即在我们的页面上,他可以用任何的邮箱地址来发邮件,如gmail,yahoo,126,163....,这样收件人在查看收到的邮件时,就会看到对应的发件人。

原来做的时候感觉也不是什么难事,我原来的做法是要求用户提供发件者的SMTP信息,如服务器、端口、用户名、密码,然后通过socket连接该服务器,发送邮件。在本地测试一切OK,上传到服务器,就不行了。服务器是在美国,提示unable to connect to server smtp.xxxx.com:25,大体意思是连不上指定地址及端口,一想就觉着是可能IP受限制了(因为最近被和谐的事情太多了),换了一个SMTP地址再试试,还不行,换了很多个,还不行,这才意思到不是这个问题。看字面上又像是连接被拒绝,防火墙似的拦截,不过我心想,不能啊,这个怎么能限制了,那还让不让人发邮件啊,于是又去绕弯子,最好折腾了一个下午,没结果,终于受不了了,发了一个ticket给客服问他有没有相关的限制,结果人家回过来,一看傻了

“Hello,
By default no account is allowed to connect to the SMTP servers of any other account. therefore, you should use "localhost" as the host address for SMTP and authenticate using one of your own email addresses that are listed in the cpanel.”

 

TND,他还真下得了手,限制了还hello!

那这样的话,就不能用用户提供的发件者信息来发邮件了,因为服务器的25、26端口都不能连出去。在baidu一下,发现其实很多服务器都限制了这个,因为怕用户用这个来群发垃圾邮件。

没办法,换一个方法,就是在构建邮件的header信息的时候,可以人为的指定一个发件地址,这样收件人在查看信件时,看到的收件人也是这个指定的邮件地址,就可以了。于是修改发送方固定为我们的一个邮箱,然后将用户填写的发件人加入到header信息中,好后试一下,可以。

证据:

虽然显示是发件人是noreply@yoberryks.com,实际上是通过另一个邮箱发送的。这个实际上就是在邮件头里面添加的,如果用默认的mail函数的话,这东西就修改不了了。

 

不知道网络上有没有相关的示例,我是没有看见到,所以放上相关代码,有用的人可以参考下。

以下代码因为是从自己写的框架里面直接复制出来的,缺少了必要的函数和类,如C,L,SArgumentException, MailStruct等,所以无法直接使用,仅做实现参考


这个是邮件构造类

 

<?
import('system.text.helper');
/*
 *邮件构造类,负责将邮件正文的信息编码为可供邮件发送类使用的内容(即eml内容)
 *原型代码,未严格测试,邮件附件的好像还不能用
 * @category   System
 * @package  System
 * @subpackage  Web.Mail
 * @author    xin478 <xin478@126.com>
 * @version $Id: mailstruct.class.php 42 2010-04-02 10:23 $
*/
class MailStruct
{
	private $mailCCs;	//抄送列表
	private $mailBCCs;	//暗送列表
	private $mailTo;			//收件人
	private $mailToADD;			//收件人邮箱	
	private $mailFrom;			//发件人		
	private $mailFromADD;		//发件人邮箱
	private $mailSubject;		//主题
    private $mailReplytoADD;    //回复到
	private $mailMessage;		//信件主体
	private $mailAttachments=array();	//附件
	private $isHtml=false;
	private $isNotify=false;
	private $charset='utf-8';
	private $boundary;
	
	public function __construct()
	{
		if (!defined('CRLF')) define('CRLF', "\r\n");
		$this->boundary ='====='.md5(rand()).'=====';
	}
	public function MailReplytoADD($mailaddress=null)
	{
		if( empty($mailaddress) ) return $this->mailToADD;
		else
		{
			if(!VerifyHelper::isEmail($mailaddress)) throw new SArgumentException( sprintf(L('_INVALID_EMAIL_ADDRESS'),$mailaddress) );
			$this->mailReplytoADD=$mailaddress;
		}
	}
	public function MailTo($name=null)
	{
		if( empty($name) ) return $this->mailTo; 
		else $this->mailTo=$name;
	}
	public function MailToAddress($mailaddress=null)
	{
		if( empty($mailaddress) ) return $this->mailToADD; 
		else
		{
			if(!VerifyHelper::isEmail($mailaddress)) throw new SArgumentException( sprintf(L('_INVALID_EMAIL_ADDRESS'),$mailaddress) ); 
			$this->mailToADD=$mailaddress;
		}
	}
	public function MailFrom($name=null)
	{
		if( empty($name) ) return $this->mailFrom; 
		else $this->mailFrom=$name;
	}
	public function MailFromAddress($mailaddress=null)
	{
		if( empty($mailaddress) ) return $this->mailFromADD; 
		else
		{
			if(!VerifyHelper::isEmail($mailaddress)) throw new SArgumentException( sprintf(L('_INVALID_EMAIL_ADDRESS'),$mailaddress) );
			$this->mailFromADD=$mailaddress;
		}
	}
	public function MailCC()
	{
		return is_array($this->mailCCs) && count($this->mailCCs)>0 ? $this->mailCCs : false;
	}
	public function MailBCC()
	{
		return is_array($this->mailBCCs) && count($this->mailBCCs)>0 ? $this->mailBCCs : false;
	}
	public function MailSubject($subject=null)
	{
		if(isset($subject)) $this->mailSubject=$subject;
		else return $this->mailSubject;
	}
	public function MailMessage($message=null)
	{
		if(isset($message)) $this->mailMessage=ltrim($message);
		else return $this->mailMessage;
	}
	public function IsHtml($isHtml=null)
	{
		if(isset($isHtml)) $this->isHtml=$isHtml;
		else return $this->isHtml;
	}	
	public function IsNotify($isNotify=null)
	{
		if(isset($isNotify)) $this->isNotify=$isNotify;
		else return $this->isNotify;
	}
	public function Charset($charset=null)
	{
		if(empty($charset)) return $this->charset;
		else $this->charset=$charset;
	}

    private function encodeEmail($address,$user=false)
    {
        if($user) return '<'.$address.'>';
        else return '"=?'.$this->charset.'?B?' . base64_encode( $user ) . '?=" <' . $address. '>"';
    }
	public function getHeader()
	{	
				
		$headers='';
		
		$headers.= 'Date: ' . gmdate('D, j M Y H:i:s') . ' +0000'.CRLF;
		$headers.= 'From: '.$this->encodeEmail($this->mailFromADD,$this->mailFrom).CRLF;
		if( $this->mailReplytoADD) $headers.='Reply-TO: '. $this->mailFromADD . '>'.CRLF;
        
        if( $this->mailToADD)
                $headers.= 'To: '.$this->encodeEmail($this->mailToADD,$this->mailTo).CRLF;
        
		if( is_array($this->mailCCs) && count($this->mailCCs)>0)
		{
			foreach($this->mailCCs as $k=>$v) $ccs.=' '.$this->encodeEmail($k,$v).',';
			if(isset($ccs)) $headers.='Cc: '.substr($ccs,0,-1).CRLF;
		}	 

		$headers.= 'Subject: =?'.$this->charset.'?B?' . base64_encode($this->mailSubject).'?=' . CRLF;
		$headers.= 'Message-ID: <' . time() .   '.' . $this->mailFromADD . '>'.CRLF;		
		$headers.= 'Mime-Version: 1.0'."".CRLF;  
		
		if ($this->isNotify)
			$headers.= 'Disposition-Notification-To: =?'.$this->charset.'?B?' . base64_encode($this->mailFrom) . '?='.'" <' . $this->mailFromADD . '>'.CRLF;
		
		if(count($this->mailAttachments)==0)
			$headers.= 'Content-Type: multipart/alternative;'.CRLF."\t".'boundary="'.$this->boundary.'"'.CRLF;
		else 
			$headers.= 'Content-Type: multipart/mixed;'.CRLF."\t".'boundary="'.$this->boundary.'"'.CRLF;		
		$headers.=CRLF.CRLF.CRLF;
		
		return str_replace(CRLF . '.', CRLF . '..', $headers);
		
	}
	public function getMessage()
	{	
		$bodys="";
		$bodys.= "This is a multi-part message in MIME format.".CRLF.CRLF;
		
		$bodys.= "--" . $this->boundary.CRLF;
		$bodys.= "Content-Type: text/plain;".CRLF."\tcharset=\"us-ascii\"".CRLF;
		$bodys.= "Content-Transfer-Encoding: base64".CRLF.CRLF;
		$bodys.= base64_encode(strip_tags(str_replace('<br/>',"\n",$this->mailMessage))).CRLF.CRLF;
		
		$bodys.= "--" . $this->boundary.CRLF;
		
		if($this->isHtml)
		{
			$bodys.= "Content-Type: text/html;".CRLF."\tcharset=\"us-ascii\"".CRLF;
			$bodys.= 'Content-Transfer-Encoding: base64'.CRLF.CRLF;
			$bodys.=base64_encode($this->mailMessage).CRLF.CRLF;
		}
        
		foreach($this->mailAttachments as $filename)
		{				
			$contentType = self::getContentType($filename);
			$attachment = base64_encode(file_get_contents($filename));
			$attachment = chunk_split($attachment);
			
			$bodys.= "--".$this->boundary.CRLF; 
			$bodys.= "Content-type: " . $contentType . "; name=".basename($filename) .CRLF;
			$bodys.= "Content-disposition: attachment; filename=".basename($filename) .CRLF;
			$bodys.= "Content-transfer-encoding: base64".CRLF.CRLF;
			$bodys.= $attachment .CRLF.CRLF;
		} 
		$bodys.= "--" . $this->boundary."--".CRLF;
		
		$bodys    = str_replace(CRLF . '.', CRLF . '..', $bodys);
		$bodys    = substr($bodys, 0, 1) == '.' ? '.' . $bodys : $bodys;

		return $bodys;
	}
	public function addBCC($mailaddress,$name=null)
	{
		if(!VerifyHelper::isEmail($mailaddress)) throw new SArgumentException( sprintf(L('_INVALID_EMAIL_ADDRESS'),$mailaddress) ); 
		
		$address=strtolower($mailaddress);
		$this->mailBCCs[$address]= empty($name)? substr($address,0, strpos($address,'@')) : $name;
	}
	public function addCC($mailaddress,$name=null)
	{
		if(!VerifyHelper::isEmail($mailaddress)) throw new SArgumentException( sprintf(L('_INVALID_EMAIL_ADDRESS'),$mailaddress) ); 
		
		$address=strtolower($mailaddress);
		$this->mailCCs[$address]= empty($name)? substr($address,0, strpos($address,'@')) : $name;
	}
	public function addAttachments($path)
	{
		if(!in_array($path,$this->mailAttachments)) $this->mailAttachments[]=$path;
	}
	
	static function getContentType($inFileName)
	{   
		//--去除路径   
		$inFileName = basename($inFileName);   
		//--去除没有扩展名的文件   
		if(strrchr($inFileName, ".") == false)
		{  
			return "application/octet-stream";  
		}  
		//--提区扩展名并进行判断   
		$extension = strrchr($inFileName, ".");   
		switch($extension){   
			case ".gif": return "image/gif";  
			case ".gz": return "application/x-gzip";  
			case ".htm": return "text/html"; 
			case ".html": return "text/html";   
			case ".jpg": return "image/jpeg";   
			case ".tar": return "application/x-tar";   
			case ".txt": return "text/plain";   
			case ".zip": return "application/zip";   
			default: return "application/octet-stream";   
		}     
	}  
	
} 
?>

 

 

 这个是邮件发送类

 

<?php
/**
 * 利用SOCKET发送SMTP邮件
 * 因为是从自己写的框架里面直接复制出来的,直接使用不能使用,少了几个函数和类,如C,L,SArgumentException等,所以仅做参考
 */

class SocketSmtp
{

	private $host,$port,$user,$pass,$auth;
	private $timeout = 10;
	private $socket;
	private $helo='localhost';
	private $status=0;
	
	public function __construct($host,$user='',$pass='',$port=25,$auth=1)
	{
		if (!defined('CRLF')) define('CRLF', "\r\n");
		$this->host=$host;
		$this->port=$port;
		$this->helo=$host;
		$this->user=$user;
		$this->pass=$pass;
		$this->auth=$auth;
	}
	
	/**
     * 获取或设置连接超时时长(秒)
     * @param <int> $second
     * @return <int> 
     */
    public function timeOut($second=null)
	{
		if( is_numeric($second) && $second>0) $this->timeout=$second;
		else return $this->timeout;
	}

    /**
     * 发送指定邮件并返回是否成功
     * @param MailStruct $mail
     * @return <bool>
     */
	public function Send($mail)
	{
		if(! $mail instanceof MailStruct) throw new SArgumentException(L('_INVALID_ARGUMENT_TYPE_','MailStruct'),$mail);		
		
		if(!$this->connect() || !$this->auth() || !$this->mail( $mail->MailFromAddress() )) return false;

        $arrToEmail = array();
		if($mail->mailCC() )
		{
			$ccs=array_keys($mail->mailCC());
			$arrToEmail = array_merge($arrToEmail,$ccs );
		}
		if($mail->mailBCC() )
		{
			$bccs=array_keys($mail->mailBCC());
			$arrToEmail = array_merge($arrToEmail, $bccs);
		}

        if($mail->MailToAddress() ) $this->rcpt($mail->MailToAddress());


		foreach($arrToEmail as $toEmail)
		{
			$this->rcpt($toEmail);
		}
		if (!$this->data()) return false;
				
		$this->send_data($mail->getHeader());
		$this->send_data('');
		$this->send_data($mail->getMessage());
		$this->send_data('.');
		
		return (substr($this->get_data(), 0, 3) === '250');		
	}

    /**
     * 关闭连接到服务的链接
     */
	public function close()
	{
		if($this->socket!=null)
		{
			$this->send_data('QUIT');
			@fclose($this->socket);
			$this->status=0;
			$this->socket = null;
		}
	}
	
	private function mail($from)
	{
		if ($this->send_data('MAIL FROM:<' . $from . '>')
				&& substr($err=$this->get_data(), 0, 3) === '250' )
		{
			
			return true;
		}
		else
		{
			return false;
		}
	}
	
	private function auth()
	{
		if($this->status<2) return false;
		
		if(!$this->auth) return true;
		
		if (is_resource($this->socket)
				&& $this->send_data('AUTH LOGIN')
				&& substr($error = $this->get_data(), 0, 3) === '334'
				&& $this->send_data(base64_encode($this->user))            // Send username
				&& substr($error = $this->get_data(),0,3) === '334'
				&& $this->send_data(base64_encode($this->pass))            // Send password
				&& substr($error = $this->get_data(),0,3) === '235' )
		{
			$this->status=3;
			return true;
		}
		else
		{
            throw new SystemException( L('_ERROR_AUTHORIZE_'), trim(substr($error, 3)));			
			return false;
		}
	}
	
	private function rcpt($to)
	{
		if ($this->send_data('RCPT TO:<' . $to . '>') && substr($error = $this->get_data(), 0, 2) === '25')
		{
			return true;
		}
		else
		{
            throw new SystemException( L('_ERROR_COMMAND_RESPONSE_'), trim(substr($error, 3)));
			return false;
		}
	}
	
	private function data()
	{
		if (is_resource($this->socket) && $this->send_data('DATA') && substr($error = $this->get_data(), 0, 3) === '354' )
		{
			return true;
		}
		else
		{
			throw new SystemException( L('_ERROR_COMMAND_RESPONSE_'), trim(substr($error, 3)));
			return false;
		}
	}	
	
	private function send_data($data)
	{
		return fwrite($this->socket, $data . CRLF, strlen($data) + 2);
	}
	
	private function get_data()
	{
		$return = '';
		$line   = '';
		
		if (is_resource($this->socket))
		{
			while (strpos($return, CRLF) === false OR $line{3} !== ' ')
			{
				$line    = fgets($this->socket, 512);
				$return .= $line;
			}
			
			return trim($return);
		}
		else
		{
			return '';
		}
	}
	
	private function connect()
	{
		if( $this->status>0 && is_resource($this->socket)) return true;
		
		$this->socket = fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
        if(!$this->socket) throw new SystemException( L('_CONNECT_SERVER_FAIL_'),$errstr);

        $this->get_data();
        @stream_set_timeout($this->socket, 0, 250000);
        $this->status=1;
        $cmd=$this->auth ?'EHLO ':'HELO ';
		
        if( $this->send_data($cmd . $this->helo) && substr($error = $this->get_data(), 0, 3) === '250')
        {
            $this->status=2;
            return true;
        }
        else
        {
            throw new SystemException( L('_ERROR_COMMAND_RESPONSE_'),  trim(substr($error, 3)));
            return false;
        }
		
	}
}
?>

 

调用的方法如下

 

<?php
$m=new MailStruct();
$m->MailSubject('test topic'); //设置标题
$m->MailMessage('this is content '); //设置正文
$m->isHtml(1); //以HTML发送
$m->MailTo('xin478'); //收件人姓名
$m->MailToAddress('44522303@qq.com'); //收件人地址
$m->MailFromAddress('xin478@126.com'); //发件人地址
$m->MailFrom( 'xin478'); //发件人姓名
$m->addBCC('bcc1@test.com','bcc person 1'); //添加密抄
$m->addCC('cc2@test.com','cc person 2'); //添加抄送
$sock=new SocketSmtp('smtp.xxx.com','user','password',25);
$sock->Send($m);
$sock->close();
?>
posted @ 2010-10-15 10:46  xin478  阅读(2652)  评论(0编辑  收藏  举报