How to add attachments to email in Java using OutputStream?
Solution 1
Yes, this is possible. The answer employing ByteArrayDataSource does not provide a satisfactory solution for large attachments because it requires that the entire content reside in memory at once. A better solution is to use a DataHandler that is fed by a PipedInputStream, which in turn is written to by a PipedOutputStream. Of course, this requires a second Thread. The code below demonstrates this:
import com.sun.mail.smtp.*;
import java.io.InputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Date;
import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.*;
import javax.mail.internet.*;
public class javamail {
// Piped Data Source
private static class PipedDataSource implements DataSource {
InputStream in;
String type;
public PipedDataSource (InputStream in, String type) { this.in = in; this.type = type; }
public String getContentType() { return type; }
public InputStream getInputStream() { return in; }
public String getName() { return "DataSource"; }
public OutputStream getOutputStream() throws IOException { throw new IOException("No OutputStream"); }
}
// Main Method
public static void main(String [] args) throws Exception {
final int BUFFER_SIZE = 32768;
Properties properties = new Properties();
properties.put("mail.smtp.starttls.enable", System.getProperty("mail.smtp.starttls.enable","true"));
properties.put("mail.smtp.ssl.trust", System.getProperty("mail.smtp.ssl.trust","*"));
properties.put("mail.smtp.host", System.getProperty("mail.smtp.host","localhost"));
properties.put("mail.smtp.port", System.getProperty("mail.smtp.port","587"));
Session session = Session.getInstance(properties);
String host = properties.getProperty("mail.smtp.host");
int port = Integer.parseInt(properties.getProperty("mail.smtp.port"));
System.err.println("connect: smtp://"+host+":"+port); System.err.flush();
MimeMessage msg = new MimeMessage(session);
PipedInputStream in = new PipedInputStream(BUFFER_SIZE);
PipedOutputStream out = new PipedOutputStream(in);
// Set general headers
msg.setFrom(System.getProperty("mail.from","Unknown <[email protected]>"));
msg.setRecipients(Message.RecipientType.TO, System.getProperty("mail.to", "[email protected]"));
msg.setSentDate(new Date());
msg.setSubject("JavaMail Test");
msg.setHeader("X-Mailer", "JavaMail");
// Set main text - Part 1 - content provided here
MimeBodyPart part1 = new MimeBodyPart();
StringBuilder sb = new StringBuilder();
sb.append("This is the cover letter that describes the accompanying \n");
sb.append("attachment, which is a base64 encoded text document of \n");
sb.append("little more value than a demonstration.\n\n");
part1.setText(sb.toString()); // Writes a computed Content-Type Header
part1.setHeader("Content-Type","text/plain; charset=us-ascii; format=flowed; delsp=yes"); // Rewrite Header
// Set attachment - Part 2 - content provdied from another thread via a pipe
MimeBodyPart part2 = new MimeBodyPart();
part2.setDataHandler(new DataHandler(new PipedDataSource (in, "text/html"))); // Writes a Content-Type Header
part2.setHeader("Content-Type","text/plain; charset=\"utf-8\"; name=\"Lorem Ipsum.txt\""); // Rewrite Header
part2.setHeader("Content-Disposition", "attachment; filename=\"Lorem Ipsum.txt\"");
part2.setHeader("Content-Transfer-Encoding","base64");
// Join parts
MimeMultipart multipart = new MimeMultipart();
multipart.addBodyPart(part1);
multipart.addBodyPart(part2);
msg.setContent(multipart);
// Start thread to deliver content for Part 2 attachment via DataHandler
Thread t = new Thread() {
public void run() {
try {
PrintWriter w = new PrintWriter(new OutputStreamWriter(out,"UTF-8"));
w.print("Lorem ipsum dolor sit amet, ligula suspendisse nulla pretium");
w.print(", rhoncus tempor fermentum, enim integer ad vestibulum volut");
w.print("pat. Nisl rhoncus turpis est, vel elit, congue wisi enim nun");
w.print("c ultricies sit, magna tincidunt. Maecenas aliquam maecenas ");
w.print("ligula nostra, accumsan taciti. Sociis mauris in integer, a ");
w.print("dolor netus non dui aliquet, sagittis felis sodales, dolor s");
w.print("ociis mauris, vel eu libero cras. Faucibus at. Arcu habitass");
w.print("e elementum est, ipsum purus pede porttitor class, ut adipis");
w.print("cing, aliquet sed auctor, imperdiet arcu per diam dapibus li");
w.print("bero duis. Enim eros in vel, volutpat nec pellentesque leo, ");
w.print("temporibus scelerisque nec.");
w.println("");
w.println("");
w.flush(); // Ensure data completely flushed to buffer
w.close(); // closes the writer and PipedOutputStream
} catch(Exception e) { e.printStackTrace(); };
try { out.close(); } catch(Exception e) { e.printStackTrace(); }
}
};
t.start();
// Send the message on its way
SMTPTransport xp = (SMTPTransport) session.getTransport();
xp.connect();
xp.sendMessage(msg,msg.getAllRecipients());
System.err.println(xp.getLastServerResponse());
t.join();
return;
}
}
You can run this code with the following properties (edited as appropriate) defined on the command line:
-Dmail.from="[email protected]"
-Dmail.to="[email protected]"
-Dmail.smtp.host=smtp.example.com
-Dmail.smtp.port=587 or 25
The example code sends an email with a text/plain cover letter with us-ascii encoding and a text/plain attachment with utf-8 encoding with a base64 transfer encoding. It also uses STARTTLS (encrypted transfer) if the MX supports it.
Solution 2
Try using ByteArrayDataSource, like this
ByteArrayOutputStream baos = //Read the output stream
DataSource aAttachment = new ByteArrayDataSource(baos.toByteArray(),"application/octet-stream");
MimeBodyPart attachmentPart = new MimeBodyPart();
attachmentPart.setDataHandler(new DataHandler(aAttachment));
Reimius
I am a programming wizard. I cast many spells, such as hash tables and array manipulation.
Updated on July 19, 2022Comments
-
Reimius almost 2 years
I've seen the code for
javax.mail
library where you add attachments to the email doing this:MimeBodyPart attachmentPart = new MimeBodyPart(); FileDataSource fds = new FileDataSource("C:/text.txt"); attachmentPart.setDataHandler(new DataHandler(fds)); attachmentPart.setFileName("text.txt"); multipart.addBodyPart(attachmentPart);
But this requires that the file reside somewhere on this disk.
I would like to grab an
OutputStream
right from the email library and stream file contents into it directly from another place where I write to thatOutputStream
.Is this possible?
-
Reimius about 11 yearsI found this solution right after I asked the question... but this solution needs to hold the whole object in memory. I would prefer a solution that avoids this, but I will mark your answer as correct if nothing else comes up.
-
NullPointerException about 11 yearsSo where else you want to put your object. You have to store the object somewhere either in memory or file system.
-
Reimius about 11 yearsThe point of a stream is that the object is streamed through from the source to destination and only read a bit at a time, it never completely resides in memory. 'baos.toByteArray()' just stuffs the whole thing right in memory wherever that array is as bytes. I'm not reading the stream from the system doing the processing, so it should never have to store the object in a complete form.
-
Reimius about 11 yearsUnless you're trying to say that the java email classes would internally do this anyway and the streaming is thus redundant. If you can point my way to documentation proving this, I would agree.
-
Scott almost 6 yearsThe sample code above is for demonstration purposes. It may be more practical (from an object member access standpoint) in production code to reverse the function of each thread, i.e., the subordinate thread runs JavaMail and the parent thread writes to the output stream.
-
Scott over 5 yearsPlease see the answer here: stackoverflow.com/questions/15709823/… for how to stream data to java mail. I believe this should be marked as the correct answer.
-
Reimius almost 5 yearsMy co-worker looked through this and said it was legit. I don't have a need for this anymore, but I'll give you the points.
-
Scott almost 5 yearsThanks, much appreciated. It really is the proper solution. I've implemented it (although with reverse sense of the source/sink threads as indicated in my previous comment) in a J2EE server (both Tomcat and Open Liberty) for mailing rather large PDF attachments with minimal memory footprint (64KB buffer per HTTPSession).