Xeo*_*oss 138 email mime smtp content-type
当创建的电子邮件,你都应该设置内容类型来multipart/alternative
发送HTML和文本时,或multipart/mixed
发送文本和附件时.
那么,如果要发送HTML,文本和附件,该怎么办?使用两者?
Iai*_*ain 133
我今天遇到了这个挑战,我发现这些答案很有用但对我来说不够明确.
编辑:刚刚找到了Apache Commons Email,它很好地包装了这个,这意味着你不需要知道如下.
如果您的要求是电子邮件:
我发现可用于Gmail/Outlook/iPad的唯一结构是:
代码是:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
以及从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<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();
}
}
Run Code Online (Sandbox Code Playgroud)
Mik*_*een 110
使用multipart/mixed
与第一部分为multipart/alternative
及以后的零件的附件.反过来,使用text/plain
和text/html
部分中multipart/alternative
的一部分.
然后,有能力的电子邮件客户端应该识别该multipart/alternative
部件并根据需要显示文本部分或html部分.它还应将所有后续部件显示为附件.
这里需要注意的重要一点是,在多部分MIME消息中,在部件中包含部件是完全有效的.从理论上讲,嵌套可以扩展到任何深度.任何具有合理能力的电子邮件客户端都应该能够递归处理所有消息部分.
ASP*_*iRE 19
我制作了一个层次结构图,以更好地帮助可视化理想的结构。每条消息分别从叶流向根。
Microsoft 参考:身体部位的 MIME 层次结构
Microsoft 参考:MIME 消息正文部分
下面是一个示例,展示了上图中显示的层次结构,可以作为模板供您使用。它包含您应该使用的所有标头,但不包含会使您被标记为垃圾邮件的标头。
您可以将其复制并粘贴到文本文件中并将其另存为message.eml
. 然后,您可以使用 Outlook 等电子邮件客户端发送邮件。要在 Outlook 中打开它,只需双击它即可。
From: "no-reply @ Example" no-reply@example.com
To: no-reply@example.com
Subject: Important information
Message-Id: 0eb69c12-ec18-4f6c-ac14-168b2092b477@example.com
Content-Language: en-US
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="mixed_boundary"
--mixed_boundary
Content-Type: multipart/related; boundary="related_boundary"
--related_boundary
Content-Type: multipart/alternative; boundary="alternative_boundary"
--alternative_boundary
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 8bit
Dear recipient,
Please find attached three files with important information.
You may also view the three files bellow:
[cid:50eef9fb]
[cid:e338d1eb]
[cid:e98e57c2]
MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-
(c) 2023 - Example, Inc.
Message Id: 0eb69c12-ec18-4f6c-ac14-168b2092b477
--alternative_boundary
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: 8bit
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<body style="font-family: consolas, monospace;">
<p>
Dear recipient,
</p>
<p>
Please find attached three files with important information.
</p>
<p>
You may also view the three files bellow:
</p>
<ol>
<li><img src="cid:50eef9fb" alt="Red"></li>
<li><img src="cid:e338d1eb" alt="Green"></li>
<li><img src="cid:e98e57c2" alt="Blue"></li>
</ol>
<p>
MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-
</p>
<p>
(c) 2023 - Example, Inc.
</p>
<p>
Message Id: 0eb69c12-ec18-4f6c-ac14-168b2092b477
</p>
</body>
</html>
--alternative_boundary--
--related_boundary
Content-Type: image/gif; name="red"
Content-Description: red.gif
Content-Disposition: inline; filename="image001.gif";
Content-ID: <50eef9fb>
Content-Transfer-Encoding: base64
R0lGODdhIAAgAPAAAPRDNslFJiH5BAEAAAEALAAAAAAgACAAAAIehI+py+0Po5y02ouz3rz7D4bi
SJbmiabqyrbuC5sFADs=
--related_boundary
Content-Type: image/gif; name="green"
Content-Description: green.gif
Content-Disposition: inline; filename="image002.gif";
Content-ID: <e338d1eb>
Content-Transfer-Encoding: base64
R0lGODdhIAAgAPAAAC59MslFJiH5BAEAAAEALAAAAAAgACAAAAIehI+py+0Po5y02ouz3rz7D4bi
SJbmiabqyrbuC5sFADs=
--related_boundary
Content-Type: image/gif; name="blue"
Content-Description: blue.gif
Content-Disposition: inline; filename="image003.gif";
Content-ID: <e98e57c2>
Content-Transfer-Encoding: base64
R0lGODdhIAAgAPAAABVlwMlFJiH5BAEAAAEALAAAAAAgACAAAAIehI+py+0Po5y02ouz3rz7D4bi
SJbmiabqyrbuC5sFADs=
--related_boundary--
--mixed_boundary
Content-Type: image/gif; name="red"
Content-Description: red.gif
Content-Disposition: attachment; filename="image004.gif";
Content-Transfer-Encoding: base64
R0lGODdhIAAgAPAAAPRDNslFJiH5BAEAAAEALAAAAAAgACAAAAIehI+py+0Po5y02ouz3rz7D4bi
SJbmiabqyrbuC5sFADs=
--mixed_boundary
Content-Type: image/gif; name="green"
Content-Description: green.gif
Content-Disposition: attachment; filename="image005.gif";
Content-Transfer-Encoding: base64
R0lGODdhIAAgAPAAAC59MslFJiH5BAEAAAEALAAAAAAgACAAAAIehI+py+0Po5y02ouz3rz7D4bi
SJbmiabqyrbuC5sFADs=
--mixed_boundary
Content-Type: image/gif; name="blue"
Content-Description: blue.gif
Content-Disposition: attachment; filename="image006.gif";
Content-Transfer-Encoding: base64
R0lGODdhIAAgAPAAABVlwMlFJiH5BAEAAAEALAAAAAAgACAAAAIehI+py+0Po5y02ouz3rz7D4bi
SJbmiabqyrbuC5sFADs=
--mixed_boundary--
Run Code Online (Sandbox Code Playgroud)
这就是 JSON 的结构看起来多么奇怪。
From: "no-reply @ Example" no-reply@example.com
To: no-reply@example.com
Subject: Important information
Message-Id: 0eb69c12-ec18-4f6c-ac14-168b2092b477@example.com
Content-Language: en-US
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="mixed_boundary"
--mixed_boundary
Content-Type: multipart/related; boundary="related_boundary"
--related_boundary
Content-Type: multipart/alternative; boundary="alternative_boundary"
--alternative_boundary
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 8bit
Dear recipient,
Please find attached three files with important information.
You may also view the three files bellow:
[cid:50eef9fb]
[cid:e338d1eb]
[cid:e98e57c2]
MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-
(c) 2023 - Example, Inc.
Message Id: 0eb69c12-ec18-4f6c-ac14-168b2092b477
--alternative_boundary
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: 8bit
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<body style="font-family: consolas, monospace;">
<p>
Dear recipient,
</p>
<p>
Please find attached three files with important information.
</p>
<p>
You may also view the three files bellow:
</p>
<ol>
<li><img src="cid:50eef9fb" alt="Red"></li>
<li><img src="cid:e338d1eb" alt="Green"></li>
<li><img src="cid:e98e57c2" alt="Blue"></li>
</ol>
<p>
MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-76-CHAR-MAX-
</p>
<p>
(c) 2023 - Example, Inc.
</p>
<p>
Message Id: 0eb69c12-ec18-4f6c-ac14-168b2092b477
</p>
</body>
</html>
--alternative_boundary--
--related_boundary
Content-Type: image/gif; name="red"
Content-Description: red.gif
Content-Disposition: inline; filename="image001.gif";
Content-ID: <50eef9fb>
Content-Transfer-Encoding: base64
R0lGODdhIAAgAPAAAPRDNslFJiH5BAEAAAEALAAAAAAgACAAAAIehI+py+0Po5y02ouz3rz7D4bi
SJbmiabqyrbuC5sFADs=
--related_boundary
Content-Type: image/gif; name="green"
Content-Description: green.gif
Content-Disposition: inline; filename="image002.gif";
Content-ID: <e338d1eb>
Content-Transfer-Encoding: base64
R0lGODdhIAAgAPAAAC59MslFJiH5BAEAAAEALAAAAAAgACAAAAIehI+py+0Po5y02ouz3rz7D4bi
SJbmiabqyrbuC5sFADs=
--related_boundary
Content-Type: image/gif; name="blue"
Content-Description: blue.gif
Content-Disposition: inline; filename="image003.gif";
Content-ID: <e98e57c2>
Content-Transfer-Encoding: base64
R0lGODdhIAAgAPAAABVlwMlFJiH5BAEAAAEALAAAAAAgACAAAAIehI+py+0Po5y02ouz3rz7D4bi
SJbmiabqyrbuC5sFADs=
--related_boundary--
--mixed_boundary
Content-Type: image/gif; name="red"
Content-Description: red.gif
Content-Disposition: attachment; filename="image004.gif";
Content-Transfer-Encoding: base64
R0lGODdhIAAgAPAAAPRDNslFJiH5BAEAAAEALAAAAAAgACAAAAIehI+py+0Po5y02ouz3rz7D4bi
SJbmiabqyrbuC5sFADs=
--mixed_boundary
Content-Type: image/gif; name="green"
Content-Description: green.gif
Content-Disposition: attachment; filename="image005.gif";
Content-Transfer-Encoding: base64
R0lGODdhIAAgAPAAAC59MslFJiH5BAEAAAEALAAAAAAgACAAAAIehI+py+0Po5y02ouz3rz7D4bi
SJbmiabqyrbuC5sFADs=
--mixed_boundary
Content-Type: image/gif; name="blue"
Content-Description: blue.gif
Content-Disposition: attachment; filename="image006.gif";
Content-Transfer-Encoding: base64
R0lGODdhIAAgAPAAABVlwMlFJiH5BAEAAAEALAAAAAAgACAAAAIehI+py+0Po5y02ouz3rz7D4bi
SJbmiabqyrbuC5sFADs=
--mixed_boundary--
Run Code Online (Sandbox Code Playgroud)
spl*_*out 17
消息包含内容.内容可以是text,html,DataHandler或Multipart,只能有一个内容.多部件只有BodyParts,但可以有多个.与消息一样,BodyParts可以包含已经描述过的内容.
带有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 )
Run Code Online (Sandbox Code Playgroud)
以及构建此类消息的代码:
// 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);
Run Code Online (Sandbox Code Playgroud)
我遇到了这个问题.这个架构(来自Lain的答案)对我有用.这是Python的解决方案.
这是主要的电子邮件创建功能:
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())}
Run Code Online (Sandbox Code Playgroud)
以下是发送包含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()
Run Code Online (Sandbox Code Playgroud)
小智 7
这是最好的: 带有附件和内嵌图像的多部分/混合 MIME 消息
和图像: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--
.
Run Code Online (Sandbox Code Playgroud)
模式多部分/相关/替代
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--
|
.
Run Code Online (Sandbox Code Playgroud)
基于Iain的例子,我有类似的需要用单独的明文,HTML和多个附件组成这些电子邮件,但使用PHP.由于我们使用Amazon SES发送带附件的电子邮件,因此API目前要求您使用sendRawEmail(...)函数从头开始构建电子邮件.
经过大量调查(并且比正常的挫折感更大),问题得到解决,并且发布了PHP源代码,以便它可以帮助其他人遇到类似的问题.希望这可以帮助别人 - 我被迫解决这个问题的猴子队伍现在已经筋疲力尽了.
PHP源代码,用于使用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 = "";
}
?>
Run Code Online (Sandbox Code Playgroud)
小智 5
很棒的答案!
我做了一些事情来使这项工作在更广泛的设备中完成.最后,我将列出我测试的客户端.
我添加了一个新的构建构造函数,它不包含参数附件,并且没有使用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;
}
Run Code Online (Sandbox Code Playgroud)在addTextVersion方法中,我在添加内容时添加了charset,这可能/应该传入,但我只是静态添加它.
textPart.setContent(messageText, "text/plain");
to
textPart.setContent(messageText, "text/plain; charset=UTF-8");
Run Code Online (Sandbox Code Playgroud)最后一项是添加到addImagesInline方法.我添加了通过以下代码将图像文件名设置为标题.如果你不这样做,那么至少在Android默认邮件客户端上,它将具有名称为"未知"的内联图像,并且不会自动下载它们并显示在电子邮件中.
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);
}
Run Code Online (Sandbox Code Playgroud)最后,这是我测试的客户列表.Outlook 2010,Outlook Web App,Internet Explorer 11,Firefox,Chrome,Outlook使用Apple的本机应用程序,电子邮件通过Gmail - 浏览器邮件客户端,Internet Explorer 11,Firefox,Chrome,Android默认邮件客户端,osx IPhone默认邮件客户端,Gmail Android上的邮件客户端,iPhone上的Gmail邮件客户端,通过Yahoo的电子邮件 - 浏览器邮件客户端,Internet Explorer 11,Firefox,Chrome,Android默认邮件客户端,osx IPhone默认邮件客户端.
希望能帮助其他人.
归档时间: |
|
查看次数: |
115851 次 |
最近记录: |