Posta multipart / alternativa vs multipart / mista

Quando si creano messaggi e-mail si deve impostare Content-Type su multipart/alternative quando si inviano HTML e TEXT o multipart/mixed quando si inviano TEXT e allegati.

Quindi cosa fai se vuoi inviare HTML, testo e allegati? Usali entrambi?

Utilizzare multipart/mixed con la prima parte come multipart/alternative e le parti successive per gli allegati. A sua volta, usa parti text/plain e text/html all’interno della parte multipart/alternative .

Un client di posta elettronica capace dovrebbe quindi riconoscere la parte multipart/alternative e visualizzare la parte di testo o la parte html come necessario. Dovrebbe anche mostrare tutte le parti successive come parti di attacco.

La cosa importante da notare qui è che, nei messaggi MIME multipart, è perfettamente valido avere parti all’interno delle parti. In teoria, tale nidificazione può estendersi a qualsiasi profondità. Qualsiasi client di posta elettronica ragionevolmente capace dovrebbe quindi essere in grado di elaborare in modo ricorsivo tutte le parti del messaggio.

Oggi ho colto questa sfida e ho trovato queste risposte utili ma non abbastanza esplicite per me.

Modifica : ho appena trovato l’e -mail Apache Commons che lo avvolge bene, il che significa che non è necessario sapere sotto.

Se il tuo requisito è una email con:

  1. versioni di testo e html
  2. la versione html ha incorporato immagini (in linea)
  3. allegati

L’unica struttura che ho trovato che funziona con Gmail / Outlook / iPad è:

  • misto
    • alternativa
      • testo
      • relazionato
        • html
        • immagine in linea
        • immagine in linea
    • attaccamento
    • attaccamento

E il codice è:

 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 * eg If you pass in the image C:\Temp\https://stackoverflow.com/questions/3902455/mail-multipart-alternative-vs-multipart-mixed/dog.jpg you can use  or  and both will work * * @param messageText * @param messageHtml * @param messageHtmlInline * @param attachments * @return * @throws MessagingException */ public Multipart build(String messageText, String messageHtml, List messageHtmlInline, List 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 embeded) throws MessagingException { // HTML version final Multipart mpRelated = newChild(parent,"related"); // Html final MimeBodyPart htmlPart = new MimeBodyPart(); HashMap cids = new HashMap(); htmlPart.setContent(replaceUrlWithCids(messageHtml,cids), "text/html"); mpRelated.addBodyPart(htmlPart); // Inline images addImagesInline(mpRelated, embeded, cids); } private void addImagesInline(Multipart parent, List embeded, HashMap 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 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 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 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; } } 

E un esempio di utilizzo con Gmail

 /** * 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 messageHtmlInline, List 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 messageHtmlInline, List 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(); } } 

I messaggi hanno contenuti. Il contenuto può essere testo, html, un DataHandler o Multipart, e può esserci un solo contenuto. Multiparts ha solo BodyParts ma può avere più di uno. BodyParts, come Messaggi, può avere contenuti che sono già stati descritti.

Un messaggio con HTML, testo e un allegato può essere visualizzato gerarchicamente in questo modo:

 message mainMultipart (content for message, subType="related") ->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 ) 

E il codice per build un messaggio del genere:

  // 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); 

Basandomi sull’esempio di Iain, ho avuto una simile necessità di comporre queste e-mail con testo in chiaro, HTML e allegati multipli, ma usando PHP. Dato che stiamo utilizzando Amazon SES per inviare e-mail con allegati, l’API attualmente richiede di compilare l’e-mail da zero utilizzando la funzione sendRawEmail (…).

Dopo molte indagini (e più che normale frustrazione), il problema è stato risolto e il codice sorgente PHP è stato pubblicato in modo che potesse aiutare gli altri a sperimentare un problema simile. Spero che questo aiuti qualcuno – la truppa di scimmie che ho costretto a lavorare su questo problema sono ormai esaurite.

Codice sorgente PHP per l’invio di e-mail con allegati tramite Amazon SES.

  "[email protected]", "subject" => "Some subject", "message" => "Some email body", "from" => "[email protected]", //OPTIONAL "replyTo" => "[email protected]", //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("[email protected]", "[email protected]") * 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 = ""; } ?> 

Grande risposta!

C’erano un paio di cose che ho fatto per farlo funzionare in un insieme più ampio di dispositivi. Alla fine elencherò i clienti su cui ho provato.

  1. Ho aggiunto un nuovo costruttore di build che non conteneva gli allegati dei parametri e non utilizzava MimeMultipart (“misto”). Non è necessario mescolare se si inviano solo immagini in linea.

     public Multipart build(String messageText, String messageHtml, List 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. Nel metodo addTextVersion ho aggiunto charset quando aggiungevo contenuto che probabilmente poteva / doveva essere passato, ma l’ho appena aggiunto staticamente.

     textPart.setContent(messageText, "text/plain"); to textPart.setContent(messageText, "text/plain; charset=UTF-8"); 
  3. L’ultimo elemento è stato aggiunto al metodo addImagesInline. Ho aggiunto l’impostazione del nome file dell’immagine all’intestazione con il seguente codice. Se non lo fai, almeno sul client di posta elettronica predefinito Android avrà immagini in linea con un nome sconosciuto e non le scaricherà automaticamente e le presenteranno via email.

     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); } 

Quindi, finalmente, questa è la lista dei clienti su cui ho provato. Outlook 2010, Outlook Web App, Internet Explorer 11, Firefox, Chrome, Outlook utilizzando l’app nativa di Apple, Email che passa attraverso Gmail – Client di posta del browser, Internet Explorer 11, Firefox, Chrome, client di posta elettronica predefinito, client di posta predefinito IPhone di IPX, Gmail client di posta su Android, client di posta Gmail su IPhone, posta elettronica che passa attraverso Yahoo – Client di posta del browser, Internet Explorer 11, Firefox, Chrome, client di posta elettronica predefinito di Android, client di posta predefinito IPhone di osx.

Spero che aiuti chiunque altro.

Colpisco questo problema. Questa architettura (dalla risposta di Lain) ha funzionato per me. Ecco la soluzione in Python.

  • misto
    • alternativa
      • testo
      • relazionato
        • html
        • immagine in linea
        • immagine in linea
    • attaccamento
    • attaccamento

Ecco la principale funzione di creazione dell’email:

 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())} 

Ecco il codice completo per l’invio di un’email contenente html / testo / allegato:

 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 = "[email protected]" sender = "[email protected]" subject = "subject" msgHtml = "Hi
Html Email" msgPlain = "Hi\nPlain Email" attachment = "/path/to/file.pdf" SendMessageWithAttachment(sender, to, subject, msgHtml, msgPlain, attachment) if __name__ == '__main__': main()

Ecco il meglio: messaggio mime multipart / mixed con allegati e immagini inline

E immagine: https://www.qcode.co.uk/images/mime-nesting-structure.png

 From: [email protected] To: [email protected]@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  =0D =0D 

This is the html part of the email.

=0D =0D =0D =0D --AlternativeBoundaryString-- --RelatedBoundaryString Content-Type: image/jpgeg;name="logo.png" Content-Transfer-Encoding: base64 Content-Disposition: inline;filename="logo.png" Content-ID: amtsb2hiaXVvbHJueXZzNXQ2XHVmdGd5d2VoYmFmaGpremxidTh2b2hydHVqd255aHVpbnRyZnhu dWkgb2l1b3NydGhpdXRvZ2hqdWlyb2h5dWd0aXJlaHN1aWhndXNpaHhidnVqZmtkeG5qaG5iZ3Vy ... ... a25qbW9nNXRwbF0nemVycHpvemlnc3k5aDZqcm9wdHo7amlodDhpOTA4N3U5Nnkwb2tqMm9sd3An LGZ2cDBbZWRzcm85eWo1Zmtsc2xrZ3g= --RelatedBoundaryString Content-Type: image/jpgeg;name="masthead.png" Content-Transfer-Encoding: base64 Content-Disposition: inline;filename="masthead.png" Content-ID: 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-- .

Schema multipart/related/alternative

 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-- | .