代码之家  ›  专栏  ›  技术社区  ›  Xeoncross

邮件多部分/可选与多部分/混合

  •  127
  • Xeoncross  · 技术社区  · 14 年前

    email messages 你应该设置 Content-Type multipart/alternative 发送HTML和文本或 multipart/mixed 发送文本和附件时。

    如果你想发送HTML、文本和附件,你该怎么办?两者都用?

    7 回复  |  直到 8 年前
        1
  •  143
  •   Pang firemonkey    9 年前

    我今天遇到了这个挑战,我发现这些答案对我有用,但不够明确。

    编辑 :刚找到 Apache Commons Email

    如果您的要求是电子邮件:

    1. html版本已嵌入(内联)图像
    2. 附件

    • 混合的
        • 文本
        • 相关的
          • 内联图像
          • 内联图像
      • 附件

    代码是:

    import javax.activation.DataHandler;
    import javax.activation.DataSource;
    import javax.activation.URLDataSource;
    import javax.mail.BodyPart;
    import javax.mail.MessagingException;
    import javax.mail.Multipart;
    import javax.mail.internet.MimeBodyPart;
    import javax.mail.internet.MimeMultipart;
    import java.net.URL;
    import java.util.HashMap;
    import java.util.List;
    import java.util.UUID;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * Created by StrongMan on 25/05/14.
     */
    public class MailContentBuilder {
    
        private static final Pattern COMPILED_PATTERN_SRC_URL_SINGLE = Pattern.compile("src='([^']*)'",  Pattern.CASE_INSENSITIVE);
        private static final Pattern COMPILED_PATTERN_SRC_URL_DOUBLE = Pattern.compile("src=\"([^\"]*)\"",  Pattern.CASE_INSENSITIVE);
    
        /**
         * Build an email message.
         *
         * The HTML may reference the embedded image (messageHtmlInline) using the filename. Any path portion is ignored to make my life easier
         * e.g. If you pass in the image C:\Temp\dog.jpg you can use <img src="dog.jpg"/> or <img src="C:\Temp\dog.jpg"/> and both will work
         *
         * @param messageText
         * @param messageHtml
         * @param messageHtmlInline
         * @param attachments
         * @return
         * @throws MessagingException
         */
        public Multipart build(String messageText, String messageHtml, List<URL> messageHtmlInline, List<URL> attachments) throws MessagingException {
            final Multipart mpMixed = new MimeMultipart("mixed");
            {
                // alternative
                final Multipart mpMixedAlternative = newChild(mpMixed, "alternative");
                {
                    // Note: MUST RENDER HTML LAST otherwise iPad mail client only renders the last image and no email
                    addTextVersion(mpMixedAlternative,messageText);
                    addHtmlVersion(mpMixedAlternative,messageHtml, messageHtmlInline);
                }
                // attachments
                addAttachments(mpMixed,attachments);
            }
    
            //msg.setText(message, "utf-8");
            //msg.setContent(message,"text/html; charset=utf-8");
            return mpMixed;
        }
    
        private Multipart newChild(Multipart parent, String alternative) throws MessagingException {
            MimeMultipart child =  new MimeMultipart(alternative);
            final MimeBodyPart mbp = new MimeBodyPart();
            parent.addBodyPart(mbp);
            mbp.setContent(child);
            return child;
        }
    
        private void addTextVersion(Multipart mpRelatedAlternative, String messageText) throws MessagingException {
            final MimeBodyPart textPart = new MimeBodyPart();
            textPart.setContent(messageText, "text/plain");
            mpRelatedAlternative.addBodyPart(textPart);
        }
    
        private void addHtmlVersion(Multipart parent, String messageHtml, List<URL> embeded) throws MessagingException {
            // HTML version
            final Multipart mpRelated = newChild(parent,"related");
    
            // Html
            final MimeBodyPart htmlPart = new MimeBodyPart();
            HashMap<String,String> cids = new HashMap<String, String>();
            htmlPart.setContent(replaceUrlWithCids(messageHtml,cids), "text/html");
            mpRelated.addBodyPart(htmlPart);
    
            // Inline images
            addImagesInline(mpRelated, embeded, cids);
        }
    
        private void addImagesInline(Multipart parent, List<URL> embeded, HashMap<String,String> cids) throws MessagingException {
            if (embeded != null)
            {
                for (URL img : embeded)
                {
                    final MimeBodyPart htmlPartImg = new MimeBodyPart();
                    DataSource htmlPartImgDs = new URLDataSource(img);
                    htmlPartImg.setDataHandler(new DataHandler(htmlPartImgDs));
                    String fileName = img.getFile();
                    fileName = getFileName(fileName);
                    String newFileName = cids.get(fileName);
                    boolean imageNotReferencedInHtml = newFileName == null;
                    if (imageNotReferencedInHtml) continue;
                    // Gmail requires the cid have <> around it
                    htmlPartImg.setHeader("Content-ID", "<"+newFileName+">");
                    htmlPartImg.setDisposition(BodyPart.INLINE);
                    parent.addBodyPart(htmlPartImg);
                }
            }
        }
    
        private void addAttachments(Multipart parent, List<URL> attachments) throws MessagingException {
            if (attachments != null)
            {
                for (URL attachment : attachments)
                {
                    final MimeBodyPart mbpAttachment = new MimeBodyPart();
                    DataSource htmlPartImgDs = new URLDataSource(attachment);
                    mbpAttachment.setDataHandler(new DataHandler(htmlPartImgDs));
                    String fileName = attachment.getFile();
                    fileName = getFileName(fileName);
                    mbpAttachment.setDisposition(BodyPart.ATTACHMENT);
                    mbpAttachment.setFileName(fileName);
                    parent.addBodyPart(mbpAttachment);
                }
            }
        }
    
        public String replaceUrlWithCids(String html, HashMap<String,String> cids)
        {
            html = replaceUrlWithCids(html, COMPILED_PATTERN_SRC_URL_SINGLE, "src='cid:@cid'", cids);
            html = replaceUrlWithCids(html, COMPILED_PATTERN_SRC_URL_DOUBLE, "src=\"cid:@cid\"", cids);
            return html;
        }
    
        private String replaceUrlWithCids(String html, Pattern pattern, String replacement, HashMap<String,String> cids) {
            Matcher matcherCssUrl = pattern.matcher(html);
            StringBuffer sb = new StringBuffer();
            while (matcherCssUrl.find())
            {
                String fileName = matcherCssUrl.group(1);
                // Disregarding file path, so don't clash your filenames!
                fileName = getFileName(fileName);
                // A cid must start with @ and be globally unique
                String cid = "@" + UUID.randomUUID().toString() + "_" + fileName;
                if (cids.containsKey(fileName))
                    cid = cids.get(fileName);
                else
                    cids.put(fileName,cid);
                matcherCssUrl.appendReplacement(sb,replacement.replace("@cid",cid));
            }
            matcherCssUrl.appendTail(sb);
            html = sb.toString();
            return html;
        }
    
        private String getFileName(String fileName) {
            if (fileName.contains("/"))
                fileName = fileName.substring(fileName.lastIndexOf("/")+1);
            return fileName;
        }
    }
    

    /**
     * Created by StrongMan on 25/05/14.
     */
    import com.sun.mail.smtp.SMTPTransport;
    
    import java.net.URL;
    import java.security.Security;
    import java.util.*;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    import javax.activation.DataHandler;
    import javax.activation.DataSource;
    import javax.activation.URLDataSource;
    import javax.mail.*;
    import javax.mail.internet.*;
    
    /**
     *
     * http://stackoverflow.com/questions/14744197/best-practices-sending-javamail-mime-multipart-emails-and-gmail
     * http://stackoverflow.com/questions/3902455/smtp-multipart-alternative-vs-multipart-mixed
     *
     *
     *
     * @author doraemon
     */
    public class GoogleMail {
    
    
        private GoogleMail() {
        }
    
        /**
         * Send email using GMail SMTP server.
         *
         * @param username GMail username
         * @param password GMail password
         * @param recipientEmail TO recipient
         * @param title title of the message
         * @param messageText message to be sent
         * @throws AddressException if the email address parse failed
         * @throws MessagingException if the connection is dead or not in the connected state or if the message is not a MimeMessage
         */
        public static void Send(final String username, final String password, String recipientEmail, String title, String messageText, String messageHtml, List<URL> messageHtmlInline, List<URL> attachments) throws AddressException, MessagingException {
            GoogleMail.Send(username, password, recipientEmail, "", title, messageText, messageHtml, messageHtmlInline,attachments);
        }
    
        /**
         * Send email using GMail SMTP server.
         *
         * @param username GMail username
         * @param password GMail password
         * @param recipientEmail TO recipient
         * @param ccEmail CC recipient. Can be empty if there is no CC recipient
         * @param title title of the message
         * @param messageText message to be sent
         * @throws AddressException if the email address parse failed
         * @throws MessagingException if the connection is dead or not in the connected state or if the message is not a MimeMessage
         */
        public static void Send(final String username, final String password, String recipientEmail, String ccEmail, String title, String messageText, String messageHtml, List<URL> messageHtmlInline, List<URL> attachments) throws AddressException, MessagingException {
            Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
            final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
    
            // Get a Properties object
            Properties props = System.getProperties();
            props.setProperty("mail.smtps.host", "smtp.gmail.com");
            props.setProperty("mail.smtp.socketFactory.class", SSL_FACTORY);
            props.setProperty("mail.smtp.socketFactory.fallback", "false");
            props.setProperty("mail.smtp.port", "465");
            props.setProperty("mail.smtp.socketFactory.port", "465");
            props.setProperty("mail.smtps.auth", "true");
    
            /*
            If set to false, the QUIT command is sent and the connection is immediately closed. If set
            to true (the default), causes the transport to wait for the response to the QUIT command.
    
            ref :   http://java.sun.com/products/javamail/javadocs/com/sun/mail/smtp/package-summary.html
                    http://forum.java.sun.com/thread.jspa?threadID=5205249
                    smtpsend.java - demo program from javamail
            */
            props.put("mail.smtps.quitwait", "false");
    
            Session session = Session.getInstance(props, null);
    
            // -- Create a new message --
            final MimeMessage msg = new MimeMessage(session);
    
            // -- Set the FROM and TO fields --
            msg.setFrom(new InternetAddress(username + "@gmail.com"));
            msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipientEmail, false));
    
            if (ccEmail.length() > 0) {
                msg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(ccEmail, false));
            }
    
            msg.setSubject(title);
    
            // mixed
            MailContentBuilder mailContentBuilder = new MailContentBuilder();
            final Multipart mpMixed = mailContentBuilder.build(messageText, messageHtml, messageHtmlInline, attachments);
            msg.setContent(mpMixed);
            msg.setSentDate(new Date());
    
            SMTPTransport t = (SMTPTransport)session.getTransport("smtps");
    
            t.connect("smtp.gmail.com", username, password);
            t.sendMessage(msg, msg.getAllRecipients());
            t.close();
        }
    
    }
    
        2
  •  111
  •   MikeMurko    13 年前

    使用 multipart/mixed 第一部分为 multipart/alternative 以及附件的后续部分。依次使用 text/plain text/html 内部零件 多部分/备选方案 部分。

    一个有能力的电子邮件客户应该认识到 多部分/备选方案

    这里要注意的重要一点是,在多部分MIME消息中,在部分中包含部分是完全有效的。理论上,这种嵌套可以延伸到任何深度。任何有能力的电子邮件客户都应该能够 处理所有消息部分。

        3
  •  18
  •   splashout    5 年前

    消息有内容。内容可以是文本、html、数据处理程序或多部分,并且只能有一个内容。多部件只有车身部件,但可以有多个。正文部分,比如消息,可以包含已经描述过的内容。

    message
      mainMultipart (content for message, subType="mixed")
        ->htmlAndTextBodyPart (bodyPart1 for mainMultipart)
          ->htmlAndTextMultipart (content for htmlAndTextBodyPart, subType="alternative")
            ->textBodyPart (bodyPart2 for the htmlAndTextMultipart)
              ->text (content for textBodyPart)
            ->htmlBodyPart (bodyPart1 for htmlAndTextMultipart)
              ->html (content for htmlBodyPart)
        ->fileBodyPart1 (bodyPart2 for the mainMultipart)
          ->FileDataHandler (content for fileBodyPart1 )
    

        // the parent or main part if you will
        Multipart mainMultipart = new MimeMultipart("mixed");
    
        // this will hold text and html and tells the client there are 2 versions of the message (html and text). presumably text
        // being the alternative to html
        Multipart htmlAndTextMultipart = new MimeMultipart("alternative");
    
        // set text
        MimeBodyPart textBodyPart = new MimeBodyPart();
        textBodyPart.setText(text);
        htmlAndTextMultipart.addBodyPart(textBodyPart);
    
        // set html (set this last per rfc1341 which states last = best)
        MimeBodyPart htmlBodyPart = new MimeBodyPart();
        htmlBodyPart.setContent(html, "text/html; charset=utf-8");
        htmlAndTextMultipart.addBodyPart(htmlBodyPart);
    
        // stuff the multipart into a bodypart and add the bodyPart to the mainMultipart
        MimeBodyPart htmlAndTextBodyPart = new MimeBodyPart();
        htmlAndTextBodyPart.setContent(htmlAndTextMultipart);
        mainMultipart.addBodyPart(htmlAndTextBodyPart);
    
        // attach file body parts directly to the mainMultipart
        MimeBodyPart filePart = new MimeBodyPart();
        FileDataSource fds = new FileDataSource("/path/to/some/file.txt");
        filePart.setDataHandler(new DataHandler(fds));
        filePart.setFileName(fds.getName());
        mainMultipart.addBodyPart(filePart);
    
        // set message content
        message.setContent(mainMultipart);
    
        4
  •  8
  •   apadana    7 年前

    • 混合的
      • 可供替代的
        • 相关的
          • 内联图像
          • 内联图像
      • 附件
      • 附件

    def create_message_with_attachment(
        sender, to, subject, msgHtml, msgPlain, attachmentFile):
        """Create a message for an email.
    
        Args:
          sender: Email address of the sender.
          to: Email address of the receiver.
          subject: The subject of the email message.
          message_text: The text of the email message.
          file: The path to the file to be attached.
    
        Returns:
          An object containing a base64url encoded email object.
        """
        message = MIMEMultipart('mixed')
        message['to'] = to
        message['from'] = sender
        message['subject'] = subject
    
        message_alternative = MIMEMultipart('alternative')
        message_related = MIMEMultipart('related')
    
        message_related.attach(MIMEText(msgHtml, 'html'))
        message_alternative.attach(MIMEText(msgPlain, 'plain'))
        message_alternative.attach(message_related)
    
        message.attach(message_alternative)
    
        print "create_message_with_attachment: file:", attachmentFile
        content_type, encoding = mimetypes.guess_type(attachmentFile)
    
        if content_type is None or encoding is not None:
            content_type = 'application/octet-stream'
        main_type, sub_type = content_type.split('/', 1)
        if main_type == 'text':
            fp = open(attachmentFile, 'rb')
            msg = MIMEText(fp.read(), _subtype=sub_type)
            fp.close()
        elif main_type == 'image':
            fp = open(attachmentFile, 'rb')
            msg = MIMEImage(fp.read(), _subtype=sub_type)
            fp.close()
        elif main_type == 'audio':
            fp = open(attachmentFile, 'rb')
            msg = MIMEAudio(fp.read(), _subtype=sub_type)
            fp.close()
        else:
            fp = open(attachmentFile, 'rb')
            msg = MIMEBase(main_type, sub_type)
            msg.set_payload(fp.read())
            fp.close()
        filename = os.path.basename(attachmentFile)
        msg.add_header('Content-Disposition', 'attachment', filename=filename)
        message.attach(msg)
    
        return {'raw': base64.urlsafe_b64encode(message.as_string())}
    

    以下是发送包含html/text/attachment的电子邮件的完整代码:

    import httplib2
    import os
    import oauth2client
    from oauth2client import client, tools
    import base64
    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    from apiclient import errors, discovery
    import mimetypes
    from email.mime.image import MIMEImage
    from email.mime.audio import MIMEAudio
    from email.mime.base import MIMEBase
    
    SCOPES = 'https://www.googleapis.com/auth/gmail.send'
    CLIENT_SECRET_FILE1 = 'client_secret.json'
    location = os.path.realpath(
        os.path.join(os.getcwd(), os.path.dirname(__file__)))
    CLIENT_SECRET_FILE = os.path.join(location, CLIENT_SECRET_FILE1)
    APPLICATION_NAME = 'Gmail API Python Send Email'
    
    def get_credentials():
        home_dir = os.path.expanduser('~')
        credential_dir = os.path.join(home_dir, '.credentials')
        if not os.path.exists(credential_dir):
            os.makedirs(credential_dir)
        credential_path = os.path.join(credential_dir,
                                       'gmail-python-email-send.json')
        store = oauth2client.file.Storage(credential_path)
        credentials = store.get()
        if not credentials or credentials.invalid:
            flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
            flow.user_agent = APPLICATION_NAME
            credentials = tools.run_flow(flow, store)
            print 'Storing credentials to ' + credential_path
        return credentials
    
    def SendMessageWithAttachment(sender, to, subject, msgHtml, msgPlain, attachmentFile):
        credentials = get_credentials()
        http = credentials.authorize(httplib2.Http())
        service = discovery.build('gmail', 'v1', http=http)
        message1 = create_message_with_attachment(sender, to, subject, msgHtml, msgPlain, attachmentFile)
        SendMessageInternal(service, "me", message1)
    
    def SendMessageInternal(service, user_id, message):
        try:
            message = (service.users().messages().send(userId=user_id, body=message).execute())
            print 'Message Id: %s' % message['id']
            return message
        except errors.HttpError, error:
            print 'An error occurred: %s' % error
            return "error"
    
    def create_message_with_attachment(
        sender, to, subject, msgHtml, msgPlain, attachmentFile):
        """Create a message for an email.
    
        Args:
          sender: Email address of the sender.
          to: Email address of the receiver.
          subject: The subject of the email message.
          message_text: The text of the email message.
          file: The path to the file to be attached.
    
        Returns:
          An object containing a base64url encoded email object.
        """
        message = MIMEMultipart('mixed')
        message['to'] = to
        message['from'] = sender
        message['subject'] = subject
    
        message_alternative = MIMEMultipart('alternative')
        message_related = MIMEMultipart('related')
    
        message_related.attach(MIMEText(msgHtml, 'html'))
        message_alternative.attach(MIMEText(msgPlain, 'plain'))
        message_alternative.attach(message_related)
    
        message.attach(message_alternative)
    
        print "create_message_with_attachment: file:", attachmentFile
        content_type, encoding = mimetypes.guess_type(attachmentFile)
    
        if content_type is None or encoding is not None:
            content_type = 'application/octet-stream'
        main_type, sub_type = content_type.split('/', 1)
        if main_type == 'text':
            fp = open(attachmentFile, 'rb')
            msg = MIMEText(fp.read(), _subtype=sub_type)
            fp.close()
        elif main_type == 'image':
            fp = open(attachmentFile, 'rb')
            msg = MIMEImage(fp.read(), _subtype=sub_type)
            fp.close()
        elif main_type == 'audio':
            fp = open(attachmentFile, 'rb')
            msg = MIMEAudio(fp.read(), _subtype=sub_type)
            fp.close()
        else:
            fp = open(attachmentFile, 'rb')
            msg = MIMEBase(main_type, sub_type)
            msg.set_payload(fp.read())
            fp.close()
        filename = os.path.basename(attachmentFile)
        msg.add_header('Content-Disposition', 'attachment', filename=filename)
        message.attach(msg)
    
        return {'raw': base64.urlsafe_b64encode(message.as_string())}
    
    
    def main():
        to = "to@address.com"
        sender = "from@address.com"
        subject = "subject"
        msgHtml = "Hi<br/>Html Email"
        msgPlain = "Hi\nPlain Email"
        attachment = "/path/to/file.pdf"
        SendMessageWithAttachment(sender, to, subject, msgHtml, msgPlain, attachment)
    
    if __name__ == '__main__':
        main()
    
        5
  •  5
  •   Pang firemonkey    9 年前

    在Iain的示例的基础上,我有一个类似的需求,即使用单独的纯文本、HTML和多个附件编写这些电子邮件,但使用的是PHP。由于我们使用Amazon SES发送带有附件的电子邮件,API当前要求您使用sendRawEmail(…)函数从头开始构建电子邮件。

    PHP Source Code for sending emails with attachments using Amazon SES.

    <?php
    
    require_once('AWSSDKforPHP/aws.phar');
    
    use Aws\Ses\SesClient;
    
    /**
     * SESUtils is a tool to make it easier to work with Amazon Simple Email Service
     * Features:
     * A client to prepare emails for use with sending attachments or not
     * 
     * There is no warranty - use this code at your own risk.  
     * @author sbossen with assistance from Michael Deal
     * http://righthandedmonkey.com
     *
     * Update: Error checking and new params input array provided by Michael Deal
     * Update2: Corrected for allowing to send multiple attachments and plain text/html body
     *   Ref: Http://stackoverflow.com/questions/3902455/smtp-multipart-alternative-vs-multipart-mixed/
     */
    class SESUtils {
    
        const version = "1.0";
        const AWS_KEY = "YOUR-KEY";
        const AWS_SEC = "YOUR-SECRET";
        const AWS_REGION = "us-east-1";
        const MAX_ATTACHMENT_NAME_LEN = 60;
    
        /**
         * Usage:
            $params = array(
              "to" => "email1@gmail.com",
              "subject" => "Some subject",
              "message" => "<strong>Some email body</strong>",
              "from" => "sender@verifiedbyaws",
              //OPTIONAL
              "replyTo" => "reply_to@gmail.com",
              //OPTIONAL
              "files" => array(
                1 => array(
                   "name" => "filename1", 
                  "filepath" => "/path/to/file1.txt", 
                  "mime" => "application/octet-stream"
                ),
                2 => array(
                   "name" => "filename2", 
                  "filepath" => "/path/to/file2.txt", 
                  "mime" => "application/octet-stream"
                ),
              )
            );
    
          $res = SESUtils::sendMail($params);
    
         * NOTE: When sending a single file, omit the key (ie. the '1 =>') 
         * or use 0 => array(...) - otherwise the file will come out garbled
         * ie. use:
         *    "files" => array(
         *        0 => array( "name" => "filename", "filepath" => "path/to/file.txt",
         *        "mime" => "application/octet-stream")
         * 
         * For the 'to' parameter, you can send multiple recipiants with an array
         *    "to" => array("email1@gmail.com", "other@msn.com")
         * use $res->success to check if it was successful
         * use $res->message_id to check later with Amazon for further processing
         * use $res->result_text to look for error text if the task was not successful
         * 
         * @param array $params - array of parameters for the email
         * @return \ResultHelper
         */
        public static function sendMail($params) {
    
            $to = self::getParam($params, 'to', true);
            $subject = self::getParam($params, 'subject', true);
            $body = self::getParam($params, 'message', true);
            $from = self::getParam($params, 'from', true);
            $replyTo = self::getParam($params, 'replyTo');
            $files = self::getParam($params, 'files');
    
            $res = new ResultHelper();
    
            // get the client ready
            $client = SesClient::factory(array(
                        'key' => self::AWS_KEY,
                        'secret' => self::AWS_SEC,
                        'region' => self::AWS_REGION
            ));
    
            // build the message
            if (is_array($to)) {
                $to_str = rtrim(implode(',', $to), ',');
            } else {
                $to_str = $to;
            }
    
            $msg = "To: $to_str\n";
            $msg .= "From: $from\n";
    
            if ($replyTo) {
                $msg .= "Reply-To: $replyTo\n";
            }
    
            // in case you have funny characters in the subject
            $subject = mb_encode_mimeheader($subject, 'UTF-8');
            $msg .= "Subject: $subject\n";
            $msg .= "MIME-Version: 1.0\n";
            $msg .= "Content-Type: multipart/mixed;\n";
            $boundary = uniqid("_Part_".time(), true); //random unique string
            $boundary2 = uniqid("_Part2_".time(), true); //random unique string
            $msg .= " boundary=\"$boundary\"\n";
            $msg .= "\n";
    
            // now the actual body
            $msg .= "--$boundary\n";
    
            //since we are sending text and html emails with multiple attachments
            //we must use a combination of mixed and alternative boundaries
            //hence the use of boundary and boundary2
            $msg .= "Content-Type: multipart/alternative;\n";
            $msg .= " boundary=\"$boundary2\"\n";
            $msg .= "\n";
            $msg .= "--$boundary2\n";
    
            // first, the plain text
            $msg .= "Content-Type: text/plain; charset=utf-8\n";
            $msg .= "Content-Transfer-Encoding: 7bit\n";
            $msg .= "\n";
            $msg .= strip_tags($body); //remove any HTML tags
            $msg .= "\n";
    
            // now, the html text
            $msg .= "--$boundary2\n";
            $msg .= "Content-Type: text/html; charset=utf-8\n";
            $msg .= "Content-Transfer-Encoding: 7bit\n";
            $msg .= "\n";
            $msg .= $body; 
            $msg .= "\n";
            $msg .= "--$boundary2--\n";
    
            // add attachments
            if (is_array($files)) {
                $count = count($files);
                foreach ($files as $file) {
                    $msg .= "\n";
                    $msg .= "--$boundary\n";
                    $msg .= "Content-Transfer-Encoding: base64\n";
                    $clean_filename = self::clean_filename($file["name"], self::MAX_ATTACHMENT_NAME_LEN);
                    $msg .= "Content-Type: {$file['mime']}; name=$clean_filename;\n";
                    $msg .= "Content-Disposition: attachment; filename=$clean_filename;\n";
                    $msg .= "\n";
                    $msg .= base64_encode(file_get_contents($file['filepath']));
                    $msg .= "\n--$boundary";
                }
                // close email
                $msg .= "--\n";
            }
    
            // now send the email out
            try {
                $ses_result = $client->sendRawEmail(
                        array(
                    'RawMessage' => array(
                        'Data' => base64_encode($msg)
                    )
                        ), array(
                    'Source' => $from,
                    'Destinations' => $to_str
                        )
                );
                if ($ses_result) {
                    $res->message_id = $ses_result->get('MessageId');
                } else {
                    $res->success = false;
                    $res->result_text = "Amazon SES did not return a MessageId";
                }
            } catch (Exception $e) {
                $res->success = false;
                $res->result_text = $e->getMessage().
                        " - To: $to_str, Sender: $from, Subject: $subject";
            }
            return $res;
        }
    
        private static function getParam($params, $param, $required = false) {
            $value = isset($params[$param]) ? $params[$param] : null;
            if ($required && empty($value)) {
                throw new Exception('"'.$param.'" parameter is required.');
            } else {
                return $value;
            }
        }
    
        /**
        Clean filename function - to get a file friendly 
        **/
        public static function clean_filename($str, $limit = 0, $replace=array(), $delimiter='-') {
            if( !empty($replace) ) {
                $str = str_replace((array)$replace, ' ', $str);
            }
    
            $clean = iconv('UTF-8', 'ASCII//TRANSLIT', $str);
            $clean = preg_replace("/[^a-zA-Z0-9\.\/_| -]/", '', $clean);
            $clean = preg_replace("/[\/| -]+/", '-', $clean);
    
            if ($limit > 0) {
                //don't truncate file extension
                $arr = explode(".", $clean);
                $size = count($arr);
                $base = "";
                $ext = "";
                if ($size > 0) {
                    for ($i = 0; $i < $size; $i++) {
                        if ($i < $size - 1) { //if it's not the last item, add to $bn
                            $base .= $arr[$i];
                            //if next one isn't last, add a dot
                            if ($i < $size - 2)
                                $base .= ".";
                        } else {
                            if ($i > 0)
                                $ext = ".";
                            $ext .= $arr[$i];
                        }
                    }
                }
                $bn_size = mb_strlen($base);
                $ex_size = mb_strlen($ext);
                $bn_new = mb_substr($base, 0, $limit - $ex_size);
                // doing again in case extension is long
                $clean = mb_substr($bn_new.$ext, 0, $limit); 
            }
            return $clean;
        }
    
    }
    
    class ResultHelper {
    
        public $success = true;
        public $result_text = "";
        public $message_id = "";
    
    }
    
    ?>
    
        6
  •  5
  •   XDevOne    9 年前

    好极了!

    有几件事我做了,使这个工作在更广泛的设备集。最后我会列出我测试过的客户。

    1. 我添加了一个新的构建构造函数,它不包含参数附件,也不使用MimeMultipart(“mixed”)。如果只发送内联图像,则不需要混合。

      public Multipart build(String messageText, String messageHtml, List<URL> messageHtmlInline) throws MessagingException {
      
          final Multipart mpAlternative = new MimeMultipart("alternative");
          {
              //  Note: MUST RENDER HTML LAST otherwise iPad mail client only renders 
              //  the last image and no email
                  addTextVersion(mpAlternative,messageText);
                  addHtmlVersion(mpAlternative,messageHtml, messageHtmlInline);
          }
      
          return mpAlternative;
      }
      
    2. 在addTextVersion方法中,我在添加内容时添加了字符集,这可能/应该传入,但我只是静态地添加了它。

      textPart.setContent(messageText, "text/plain");
      to
      textPart.setContent(messageText, "text/plain; charset=UTF-8");
      
    3. for (URL img : embeded) {
          final MimeBodyPart htmlPartImg = new MimeBodyPart();
          DataSource htmlPartImgDs = new URLDataSource(img);
          htmlPartImg.setDataHandler(new DataHandler(htmlPartImgDs));
          String fileName = img.getFile();
          fileName = getFileName(fileName);
          String newFileName = cids.get(fileName);
          boolean imageNotReferencedInHtml = newFileName == null;
          if (imageNotReferencedInHtml) continue;
          htmlPartImg.setHeader("Content-ID", "<"+newFileName+">");
          htmlPartImg.setDisposition(BodyPart.INLINE);
          **htmlPartImg.setFileName(newFileName);**
          parent.addBodyPart(htmlPartImg);
      }
      

    最后,这是我测试过的客户名单。 展望2010, Outlook Web应用程序, Internet Explorer 11, 铬, 通过Gmail发送的电子邮件- 浏览器邮件客户端, 火狐, Android默认邮件客户端, Android上的Gmail邮件客户端, 通过雅虎的电子邮件- Internet Explorer 11, 火狐, 铬, Android默认邮件客户端, osx IPhone默认邮件客户端。

        7
  •  4
  •   user8660272 user8660272    7 年前

    Multipart/mixed mime message with attachments and inline images

    图像: https://www.qcode.co.uk/images/mime-nesting-structure.png

    From: from@qcode.co.uk
    To: to@@qcode.co.uk
    Subject: Example Email
    MIME-Version: 1.0
    Content-Type: multipart/mixed; boundary="MixedBoundaryString"
    
    --MixedBoundaryString
    Content-Type: multipart/related; boundary="RelatedBoundaryString"
    
    --RelatedBoundaryString
    Content-Type: multipart/alternative; boundary="AlternativeBoundaryString"
    
    --AlternativeBoundaryString
    Content-Type: text/plain;charset="utf-8"
    Content-Transfer-Encoding: quoted-printable
    
    This is the plain text part of the email.
    
    --AlternativeBoundaryString
    Content-Type: text/html;charset="utf-8"
    Content-Transfer-Encoding: quoted-printable
    
    <html>
      <body>=0D
        <img src=3D=22cid:masthead.png=40qcode.co.uk=22 width 800 height=3D80=
     =5C>=0D
        <p>This is the html part of the email.</p>=0D
        <img src=3D=22cid:logo.png=40qcode.co.uk=22 width 200 height=3D60 =5C=
    >=0D
      </body>=0D
    </html>=0D
    
    --AlternativeBoundaryString--
    
    --RelatedBoundaryString
    Content-Type: image/jpgeg;name="logo.png"
    Content-Transfer-Encoding: base64
    Content-Disposition: inline;filename="logo.png"
    Content-ID: <logo.png@qcode.co.uk>
    
    amtsb2hiaXVvbHJueXZzNXQ2XHVmdGd5d2VoYmFmaGpremxidTh2b2hydHVqd255aHVpbnRyZnhu
    dWkgb2l1b3NydGhpdXRvZ2hqdWlyb2h5dWd0aXJlaHN1aWhndXNpaHhidnVqZmtkeG5qaG5iZ3Vy
    ...
    ...
    a25qbW9nNXRwbF0nemVycHpvemlnc3k5aDZqcm9wdHo7amlodDhpOTA4N3U5Nnkwb2tqMm9sd3An
    LGZ2cDBbZWRzcm85eWo1Zmtsc2xrZ3g=
    
    --RelatedBoundaryString
    Content-Type: image/jpgeg;name="masthead.png"
    Content-Transfer-Encoding: base64
    Content-Disposition: inline;filename="masthead.png"
    Content-ID: <masthead.png@qcode.co.uk>
    
    aXR4ZGh5Yjd1OHk3MzQ4eXFndzhpYW9wO2tibHB6c2tqOTgwNXE0aW9qYWJ6aXBqOTBpcjl2MC1t
    dGlmOTA0cW05dGkwbWk0OXQwYVttaXZvcnBhXGtsbGo7emt2c2pkZnI7Z2lwb2F1amdpNTh1NDlh
    ...
    ...
    eXN6dWdoeXhiNzhuZzdnaHQ3eW9zemlqb2FqZWt0cmZ1eXZnamhka3JmdDg3aXV2dWd5aGVidXdz
    dhyuhehe76YTGSFGA=
    
    --RelatedBoundaryString--
    
    --MixedBoundaryString
    Content-Type: application/pdf;name="Invoice_1.pdf"
    Content-Transfer-Encoding: base64
    Content-Disposition: attachment;filename="Invoice_1.pdf"
    
    aGZqZGtsZ3poZHVpeWZoemd2dXNoamRibngganZodWpyYWRuIHVqO0hmSjtyRVVPIEZSO05SVURF
    SEx1aWhudWpoZ3h1XGh1c2loZWRma25kamlsXHpodXZpZmhkcnVsaGpnZmtsaGVqZ2xod2plZmdq
    ...
    ...
    a2psajY1ZWxqanNveHV5ZXJ3NTQzYXRnZnJhZXdhcmV0eXRia2xhanNueXVpNjRvNWllc3l1c2lw
    dWg4NTA0
    
    --MixedBoundaryString
    Content-Type: application/pdf;name="SpecialOffer.pdf"
    Content-Transfer-Encoding: base64
    Content-Disposition: attachment;filename="SpecialOffer.pdf"
    
    aXBvY21odWl0dnI1dWk4OXdzNHU5NTgwcDN3YTt1OTQwc3U4NTk1dTg0dTV5OGlncHE1dW4zOTgw
    cS0zNHU4NTk0eWI4OTcwdjg5MHE4cHV0O3BvYTt6dWI7dWlvenZ1em9pdW51dDlvdTg5YnE4N3Z3
    ...
    ...
    OTViOHk5cDV3dTh5bnB3dWZ2OHQ5dTh2cHVpO2p2Ymd1eTg5MGg3ajY4bjZ2ODl1ZGlvcjQ1amts
    dfnhgjdfihn=
    
    --MixedBoundaryString--
    
    .
    

    Header
    |From: email
    |To: email
    |MIME-Version: 1.0
    |Content-Type: multipart/mixed; boundary="boundary1";
    Message body
    |multipart/mixed --boundary1
    |--boundary1
    |   multipart/related --boundary2
    |   |--boundary2
    |   |   multipart/alternative --boundary3
    |   |   |--boundary3
    |   |   |text/plain
    |   |   |--boundary3
    |   |   |text/html
    |   |   |--boundary3--
    |   |--boundary2    
    |   |Inline image
    |   |--boundary2    
    |   |Inline image
    |   |--boundary2--
    |--boundary1    
    |Attachment1
    |--boundary1
    |Attachment2
    |--boundary1
    |Attachment3
    |--boundary1--
    |
    .
    
        8
  •  1
  •   AConsumer    5 年前

    混合亚型

    “multipart”的“mixed”子类型用于 零件是独立的,需要按特定顺序捆绑。 必须被视为“混合”子类型。

    替代子类型

    “multipart/alternative”类型在语法上与 “multipart/mixed”,但语义不同。特别地, 信息

    Source