Is it possible to send an email using javax.mail and using an “existing” InputStream for the email message attachment content?
Currently I am building the email message as follows:
final MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject("Subject line");
final Multipart multipartContent = new MimeMultipart();
final MimeBodyPart textPart = new MimeBodyPart();
textPart.setText("Message body");
multipartContent.addBodyPart(textPart);
final MimeBodyPart attachmentPart = new MimeBodyPart();
final DataSource source = new InputStreamDataSource("text/plain", "test.txt", new ByteArrayInputStream("CONTENT INPUT STREAM".getBytes()));
attachmentPart.setDataHandler(new DataHandler(source));
attachmentPart.setFileName("text.txt");
multipartContent.addBodyPart(attachmentPart);
message.setContent(multipartContent);
InputStreamDataSource is implemented as follows:
public class InputStreamDataSource implements DataSource
{
private final String contentType;
private final String name;
private final InputStream inputStream;
public InputStreamDataSource(String contentType, String name, InputStream inputStream)
{
this.contentType = contentType;
this.name = name;
this.inputStream = inputStream;
}
public String getContentType()
{
return contentType;
}
public String getName()
{
return name;
}
public InputStream getInputStream() throws IOException
{
System.out.println("CALLED TWICE: InputStreamDataSource.getInputStream()");
return new BufferedInputStream(inputStream);
//return new ByteArrayInputStream("THIS 'NEW' INPUT STREAM WORKS BUT 'EXISTING' INPUT STREAM RESULTS IN ZERO-BYTE ATTACHMENT".getBytes());
}
public OutputStream getOutputStream() throws IOException
{
throw new UnsupportedOperationException("Not implemented");
}
}
The DataSource provides method getInputStream() to get the InputStream for the email message attachment content.
If I return a "new" InputStream which does not depend on an "existing" InputStream then it works fine. But if I return an “existing” InputStream then the email message is delivered with a zero-byte attachment.
Is it possible to send an email using javax.mail, and use an “existing” InputStream for the email message attachment content?
EDIT:
see https://community.oracle.com/thread/1590625
TL;DR use a ByteArrayDataSource
One has to delve into Oracle's source code... https://java.net/projects/javamail/sources/mercurial/content/mail/src/main/java/javax/mail/internet/MimeBodyPart.java
The current java mail implementation goes 2 times over the input stream:
First to determine whether it should set the header "Content-Transfer-Encoding" to 7 or 8 bits (see Content Transfer Encoding 7bit or 8 bit)
Then a second time when it actually writes the message
...which kind of sucks because the whole stream (maybe hundreds of MB over a slow connection) will be read two times ...and leads to exactly this issue for streams that are "consumed" once read.
The first "workaround" I tried is to specify the headers yourself:
attachmentPart.setDataHandler(new DataHandler(source));
attachmentPart.setHeader("Content-Transfer-Encoding", "8bit");
attachmentPart.setHeader("Content-Type", ds.getContentType() + "; " + ds.getName());
...and in that order, and not the other way round ...because for some reason setDataHandler calls internally another method invalidateContentHeaders which clears the "Content-Transfer-Encoding" header again (wtf?!)
Sounded great, the mail was sent, hooray!!! :D ... :( see next
Attachment send ...but broken
The received file in my mail server is broken. Huh. Why?!. After a long search and delving again in this crappy java mail code, I found it, they pipe the InputStream into a LineOutputStream which changes the line endings of your binary data. Meh. The java mail implementation is really a mess. :/
I rewrited your InputStreamDataSource class, and it works for me.
class InputStreamDataSource implements DataSource {
String contentType;
String name;
byte[] fileData;
public InputStreamDataSource(String contentType, String name, InputStream inputStream) throws IOException {
this.contentType = contentType;
this.name = name;
/**
* It seems DataSource will close inputStream and reopen it.
* I converted inputStream to a byte array, so it won't be closed again.
*/
fileData = IOUtils.toByteArray(inputStream);
}
public String getContentType() {
return contentType;
}
public String getName() {
return name;
}
public InputStream getInputStream() throws IOException {
/**
* Convert byte array back to inputStream.
*/
return new ByteArrayInputStream(fileData);
}
public OutputStream getOutputStream() throws IOException {
throw new UnsupportedOperationException("Not implemented");
}
}
I solved it converting the InputStream to a byte array and converting it to Base64 format.
//get file name
String fileName = ...;
//get content type
String fileContentType = ...;
//get file content
InputStream fileStream = ...;
//convert to byte array
byte[] fileByteArray = IOUtils.toByteArray(fileStream);
//and convert to Base64
byte[] fileBase64ByteArray = java.util.Base64.getEncoder().encode(fileByteArray);
//manually define headers
InternetHeaders fileHeaders = new InternetHeaders();
fileHeaders.setHeader("Content-Type", fileContentType + "; name=\"" + fileName + "\"");
fileHeaders.setHeader("Content-Transfer-Encoding", "base64");
fileHeaders.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
//build MIME body part
MimeBodyPart mbp = new MimeBodyPart(fileHeaders, fileBase64ByteArray);
mbp.setFileName(fileName);
//add it to the multipart
multipart.addBodyPart(mbp);
If the InputStream contains mime headers then use the javax.mail.internet.MimeBodyPart(InputStream) constructor. You don't need to use a custom DataSource class.
Otherwise, if the InputStream is just the body without headers then convert the stream into a byte array and use the javax.mail.internet.MimeBodyPart(InternetHeaders, byte[]) constructor to provide your headers.
I use this code for sending email with web downloaded attachment. You can easily edit it for your purpose. In mimeType use mime type of your attachment. Happy coding.
try {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(
"sender#gmail.com"));
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse("reciever#gmail.com"));
message.setSubject("subject");
Multipart multipart = new MimeMultipart();
URL url = new URL(url);
InputStream is = url.openStream();
MimeBodyPart bodyPart = new MimeBodyPart(is);
multipart.addBodyPart(bodyPart);
message.setContent(multipart);
message.addHeader("Content-Type", mimeType);
Transport.send(message);
logger.info("SENT to" + message.getRecipients(RecipientType.TO));
} catch (MessagingException e) {
//some implementation
}
The current java mail implementation goes over the input stream twice: the first pass to detect the encoding for the data and the second one to send the data.
You can prevent the first pass if you specify the encoding using the EncodingAware interface. The supplied DataSource should implement this interface. Here is an example:
public class AttachementDataSource implements javax.activation.DataSource, javax.mail.EncodingAware {
private final InputStreamSource inputStreamSource;
public AttachementDataSource(InputStreamSource inputStreamSource) {
this.inputStreamSource = inputStreamSource;
}
#Override
public InputStream getInputStream() throws IOException {
return inputStreamSource.getInputStream();
}
#Override
public OutputStream getOutputStream() throws IOException {
throw new UnsupportedOperationException("Read-only javax.activation.DataSource");
}
#Override
public String getContentType() {
return "application/octet-stream";
}
#Override
public String getName() {
return "inline";
}
#Override
public String getEncoding() {
return "base64";
}
}
Related
I want to send mail with an attachment through Mailjet in java. I have no problem while sending simple mail without attachment but when I try to add attachment I am getting this error:
400
[{"ErrorIdentifier":"314408e7-e528-469f-9361-2eb3c24b2b32","ErrorCode":"mj-0004","ErrorRelatedTo":["Messages.Attachments"],"ErrorMessage":"Type mismatch. Expected type \"Attachments\".","StatusCode":400}]
And my code looks like this:
#Service
public class MailJetSenderImp implements MailJetSender {
Message message = new Message();
#Override
public ResponseEntity<Message> sendMail(String To, String Body, String Subject, File attachment) throws MailjetSocketTimeoutException, JSONException, IOException {
FileInputStream fileInputStream = new FileInputStream(attachment);
int byteLength=(int) attachment.length(); //bytecount of the file-content
byte[] filecontent = new byte[byteLength];
fileInputStream.read(filecontent,0,byteLength);
byte[] encoded = Base64.getEncoder().encode(filecontent);
MailjetRequest email = new MailjetRequest(Emailv31.resource)
.property(Emailv31.MESSAGES, new JSONArray()
.put(new JSONObject()
.put(Emailv31.Message.FROM, new JSONObject()
.put("Email","xxxxxx#gmail.com" )
.put("Name", "xxxxx"))
.put(Emailv31.Message.TO, new JSONArray()
.put(new JSONObject()
.put("Email", To)))
.put(Emailv31.Message.SUBJECT, Subject)
.put(Emailv31.Message.TEXTPART, "")
.put(Emailv31.Message.HTMLPART, Body)
.put(Emailv31.Message.ATTACHMENTS,encoded)));
final String mailjetApiKey = "xxxxxxxx";
final String mailjetSecretKey = "yyyyyyyy";
MailjetClient client = new MailjetClient(
mailjetApiKey, mailjetSecretKey, new ClientOptions("v3.1"));
try {
// trigger the API call
MailjetResponse response = client.post(email);
// Read the response data and status
System.out.println(response.getStatus());
System.out.println(response.getData());
message.setCode(response.getStatus());
message.setMessage(response.getData().toString());
return ResponseEntity.status(HttpStatus.OK).body(message);
} catch (MailjetException e) {
System.out.println("Mailjet Exception: " + e);
message.setCode(400);
message.setMessage("could not send email");
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(message);
}
}
}
I get error message on (.put(Emailv31.Message.ATTACHMENTS,encoded)));
Here is the Java code to send the mail attachment
The ATTACHMENTS data is a JSON array containing 3 fields:
ContentType - Content type of the attachment
Filename - name of the file attachment that the receiver would see
Base64Content - Base64 encoded file data as String
So, encode the file content as String ( I used the Base64 encoder from mailjet client JAR itself here ). filecontent is the byte[]. A hardcoded PDF file code sample below :
java.nio.file.Path pdfPath =
java.nio.file.Paths.get("c:\\D\\sample.pdf");
byte[] filecontent = java.nio.file.Files.readAllBytes(pdfPath);
String fileData = com.mailjet.client.Base64.encode(filecontent);
Next, use this code to send the attachment, your other code remains same.
Example here is for a PDF file, choose your MIME type correctly
.put(Emailv31.Message.ATTACHMENTS,
new JSONArray().put(new JSONObject().put("ContentType", "application/pdf")
.put("Filename", "abc.pdf")
.put("Base64Content", fileData)))
.put(Emailv31.Message.HTMLPART,...
i want to return array byte inside object on java spring rest.
my expected response object.
public class ResponseByteArray {
private String fileName;
private byte[] file;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public byte[] getFile() {
return file;
}
public void setFile(byte[] file) {
this.file = file;
}
}
#RequestMapping(value = "/getDataByte", method = RequestMethod.GET)
public ResponseEntity<?> uploadUImageGet() throws IOException {
ResponseByteArray response = new ResponseByteArray();
File fileImage = new File("D://images//download1.jpg");
byte[] fileContent = Files.readAllBytes(fileImage.toPath());
response.setFileName("testFile.jpg");
response.setFile(fileContent);
return new ResponseEntity<ResponseByteArray>(response, HttpStatus.OK);
}
but i the response of rest API is json format and value of file is string encodeBase64 instead if byte[]. what is happend to the process ? Rest can not resturn byte[] inseide object ?:
{
"fileName": "testFile.jpg",
"file": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxIQEg8PEhIPFRIPDw8QEA8QDw8PEBAPFREWFhUVFRUYHSggGBolHRUVITEhJSkrLi4uFx8zODMsNygtLisBCgoKDg0NFQ8PFSsdFR0tLS0tKysrKy0tKy0tNy03LS0rKy0tLSsrKzc3LS0rLSsrKy0rKzctLTcrLSstNy0tK//AABEIAQMAwgMBIgACEQEDEQH/xAAbAAABBQEBAAAAAAAAAAAAAAADAAECBAUGB//EAD8QAAIBAgMFBQUGBQIHAQAAAAECAAMRBCExBRJBUWEGMnGBkRMiUqHRFGJyscHwQpKisuEjkxUXM3OC0vEH/8QAGQEBAQEBAQEAAAAAAAAAAAAAAAECAwUE/8QAIBEBAQEAAwADAAMBAAAAAAAAABEBAgMSITFBEyJRB ...
}
If you're talking about Rest as a protocol, then it can return bynary stream in a sense that HTTP protocol can work with binary streams. Here is an example:
#GetMapping(value = "/images/{id}")
public ResponseEntity<byte[]> get(#PathVariable String id) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
return new ResponseEntity<>(service.get(id), headers, HttpStatus.OK);
}
However if you're talking about REST as a protocol that returns JSON/XML (which is something that I assumed reading your question), then, in this sense: REST returns textual (not binary) data (because you assume it) in a form of JSON or maybe XML.
In any case how do you represent a byte array as a textual data?
One way is to encode it into String representation which is exactly what Base64 encoding does.
Note that if you check the size of byte array and compare it to the length of the string created by Base64 encoding you'll notice that Base64 has a pretty significant overhead.
I'm trying to send a mail with an attachment file by using Javamail. But when the code is trying to read the content of the file a FileNoFound exception is raised.
The weird thing is that in debug mode I can see that it gets the file (the MultiPartFile variable isn't empy). I'm also using #Async so maybe it's the real issue.
My method sendMail :
#Async("threadPoolTaskExecutor")
public void sendMail(Context ctx, String dest, String subject, String templateName, MultipartFile attachment)
throws MessagingException, MailException, IOException {
MimeMessage mimeMessage = emailSender.createMimeMessage();
MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true, "UTF-8");
message.setFrom("test#test.com");
message.setTo(dest);
message.setSubject(subject);
String htmlContent = emailTemplateEngine.process(templateName, ctx);
message.setText(htmlContent, true); // true = isHtml
if (attachment != null) {
InputStreamSource attachmentSource;
attachmentSource = new ByteArrayResource(attachment.getBytes());
message.addAttachment(attachment.getOriginalFilename(), attachmentSource);
}
emailSender.send(mimeMessage);
}
The error I'm having is :
java.io.FileNotFoundException: C:\Users\user\AppData\Local\Temp\tomcat.970471969296194243.8080\work\Tomcat\localhost\ROOT\upload_a28782c3_18d7_4b6b_84b7_2d9e81e9a692_00000007.tmp
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at org.apache.tomcat.util.http.fileupload.disk.DiskFileItem.getInputStream(DiskFileItem.java:194)
at org.apache.catalina.core.ApplicationPart.getInputStream(ApplicationPart.java:100)
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile.getBytes(StandardMultipartHttpServletRequest.java:245)
[...]
Edit: I tried removing the #Async and the file is uploaded without any errors. So this is the cause of my FileNotFound exception... So is there any way to solve this because I would very much like to put this method on async. I don't know much about async and thread managing so maybe I just can't use it when uploading files (?)
your code does not seem to explain what is emailTemplateEngine is.
usually your server does not copy custom templates by default. check dir C:\Users\user\AppData\Local\Temp\tomcat.970471969296194243.8080\work\Tomcat\localhost\ROOT\
it should be defined out of the box.
you can try using freemarker.template.Configuration
private Configuration initialiseFreeMarkerConfiguration() throws IOException, TemplateException {
FreeMarkerConfigurationFactoryBean factory = new FreeMarkerConfigurationFactoryBean();
factory.setTemplateLoaderPath("classpath:/ROOT/");
factory.setPreferFileSystemAccess(false);
return factory.createConfiguration();
}
your code...
private final Configuration freeMarkerConfiguration;
// in method
MimeMessage mimeMessage = emailSender.createMimeMessage();
MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true, "UTF-8");
message.setFrom("test#test.com");
message.setTo(dest);
message.setSubject(subject);
message.setText(processEmailFromTemplate(model), true);
//attachment logic, use FileSystemResource instead of ByteArrayResource
if (attachment != null) {
InputStreamSource attachmentSource;
FileSystemResource file = new FileSystemResource(new File(attachment));
message.addAttachment(attachment.getOriginalFilename(), file );
}
emailSender.send(mimeMessage);
method processEmailFromTemplate() should do something like this.
private String processEmailFromTemplate(Map<String, Object> model) throws IOException, TemplateException {
StringBuilder content = new StringBuilder();
content.append(FreeMarkerTemplateUtils.processTemplateIntoString(freeMarkerConfiguration.getTemplate("your template name"), model));
return content.toString();
}
I'm using Oreilly MultipartRequest servlet to parse a forms multipart/form-data request as so:
String path = getServletContext().getRealPath("/tmp");
List uploadFiles = new ArrayList();
MultipartRequest multi = new MultipartRequest(request, path, 50 * 1024 * 1024, "UTF-8");
Enumeration<String> params = multi.getParameterNames();
Enumeration<String> files = multi.getFileNames();
//retrieve text parameters
String param;
while(params .hasMoreElements()){
param = params .nextElement();
for(String occurence : multi.getParameterValues(param)){
pageContext.setAttribute(param, occurence);
}
}
//get files
String fileParam;
while(files.hasMoreElements()){
fileParam = files.nextElement();
uploadFiles.add(multi.getFile(fileParam));
}
I'm able to get all the data, but in regards of files, I want to send them as an attachment, typically this is done as such (by parsing the request)
ServletFileUpload servletFileUpload = new ServletFileUpload(new DiskFileItemFactory());
try {
List fileItemsList = servletFileUpload.parseRequest(request);
Iterator it = fileItemsList.iterator();
while (it.hasNext()){
FileItem fileItem = (FileItem)it.next();
if (fileItem.isFormField()){
messageBodyPart = new MimeBodyPart();
DataSource source = new FileDataSource(fileItem.getString());
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(fileItem.getFieldName());
multipart.addBodyPart(messageBodyPart);
}
}
} catch(Exception e) {
out.println(e.toString());
}
Instead, I am creating a list of all files uploadFiles to upload. The problem is the attachment uses FileItem interface which allows me to get the contents of the file item as a String using getString(), whereas Oreilly MultipartRequest only returns the file as a File object. How can I use the content of the File Object similarly, or cast it into a FileItem object perhaps.
According to the documentation, FileItem.getString() returns "the contents of the file item as a String". But FileDataSource(String) "creates a FileDataSource from the specified path name". So in your second code fragment you are passing a file contents as a String (which who knows what will do for binary files) instead of a file name. And getFieldName() will return the name of the form field and not the name of the upload file (which is what you want).
If you processed the upload with MultipartRequest then it's trivial to instantiate the FileDataSource required by DataHandler from the File objects returned by MultipartRequest.
If you processed the upload with ServletFileUpload then probably the best is to create your own custom implementation of DataSource interface which allows a FileItem in the constructor
public class FileItemDataSource implements DataSource {
private FileItem item;
public FileItemDataSource(FileItem item) {
this.item = item;
}
#Override
public String getContentType() {
return item.getContentType() ;
}
#Override
public InputStream getInputStream() throws IOException {
return item.getInputStream();
}
#Override
public String getName() {
return item.getName();
}
#Override
public OutputStream getOutputStream() throws IOException {
return item.getOutputStream();
}
}
with such class you could do
DataSource source = new FileItemDataSource(fileItem);
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(fileItem.getName());
multipart.addBodyPart(messageBodyPart);
Apparently the class FileDataSource accepts a File or String at it's constructor. So passing the File object directly simply solves the problem. As such:
File fileItem = (File)it.next();
DataSource source = new FileDataSource(fileItem);
My scenario is this:
I have a web application, the user writes a message, attaches a file and sends the email.
I use JavaMail to send the mail like this but i have problem attaching the file to the message (my file is on Session):
if (request.getSession().getAttribute("EMAIL_ATTACHMENT") != null) {
UploadFile file = (UploadFile) request.getSession().getAttribute("EMAIL_ATTACHMENT");
MimeBodyPart mbp1 = new MimeBodyPart();
mbp1.setContent(text, "text/html;charset=UTF-8");
MimeMultipart mp = new MimeMultipart();
mp.addBodyPart(mbp1);
MimeBodyPart mbp2 = new MimeBodyPart();
// attach the file to the message
MyMailAttachmentDataSource fds = new MyMailAttachmentDataSource(file);
mbp2.setDataHandler(new DataHandler(fds));
mbp2.setFileName(fds.getName());
mp.addBodyPart(mbp2);
msg.setContent(mp, "text/plain");
}
The code for MyMailAttachmentDataSource is this:
public class MyMailAttachmentDataSource implements DataSource{
private UploadFile file;
public MyMailAttachmentDataSource(UploadFile file){
this.file=file;
}
#Override
public InputStream getInputStream() throws IOException {
return file.getInpuStream();
}
#Override
public OutputStream getOutputStream() throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
#Override
public String getContentType() {
return file.getContentType();
}
#Override
public String getName() {
return file.getFileName();
}
}
When i try to send the email, i get this exception
java.io.IOException: "text/plain" DataContentHandler requires String object, was given object of type class javax.mail.internet.MimeMultipart
at com.sun.mail.handlers.text_plain.writeTo(text_plain.java:97)
at javax.activation.ObjectDataContentHandler.writeTo(DataHandler.java:884)
at javax.activation.DataHandler.writeTo(DataHandler.java:317)
at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:1089)
at javax.mail.internet.MimeMessage.writeTo(MimeMessage.java:1527)
at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:321)
at admin.email.JavaMail.SendEmail(JavaMail.java:403)
at admin.email.MailSend.SendMail(MailSend.java:86)
I tried to change the msg.contentType to "text/html" but still get the above Exception
"text/html" DataContentHandler requires String object, was given object of type class javax.mail.internet.MimeMultipart
Does anyone knows what causes this error and how can I fix it?
Email with attachment can not be of type text/plain or text/html, it should be multipart/mixed.
It seems, that it will be enough to change code line msg.setContent(mp, "text/plain"); to
just msg.setContent(mp);