How to add attachments to email in Java using OutputStream?
Asked Answered
Z

2

11

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

Is this possible?

Zackaryzacks answered 29/3, 2013 at 19:5 Comment(0)
M
5

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.

Update: Starting with Java9 the javax.activation.* classes have moved. Look here for the solution.

Merrow answered 29/1, 2018 at 2:21 Comment(3)
The 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.Merrow
My 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.Zackaryzacks
Thanks, 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).Merrow
T
11

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));
Tarpley answered 29/3, 2013 at 19:40 Comment(5)
I 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.Zackaryzacks
So where else you want to put your object. You have to store the object somewhere either in memory or file system.Tarpley
The 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.Zackaryzacks
Unless 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.Zackaryzacks
Please see the answer here: #15710323 for how to stream data to java mail. I believe this should be marked as the correct answer.Merrow
M
5

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.

Update: Starting with Java9 the javax.activation.* classes have moved. Look here for the solution.

Merrow answered 29/1, 2018 at 2:21 Comment(3)
The 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.Merrow
My 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.Zackaryzacks
Thanks, 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).Merrow

© 2022 - 2024 — McMap. All rights reserved.