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();
}
Related
I am trying to implement send email with attachment in spring boot. I have a contact form with various fields. One of the field is to upload some file and send along with other fields. Like username, company name, number of units, insurance copy (type file) and when submitting form. Request goes to controller where function for post mapping is written. I want to send an email to the user with attachment that has been uploaded from client side.
#PostMapping(value = {WebUrl.queryMail})
public ModelAndView queryMail(HttpServletRequest request, #RequestParam("username") String username,
#RequestParam("companyname") String companyname, #RequestParam("mcnumber") String mcnumber, #RequestParam("dotnumber") String dotnumber,
#RequestParam("trailertype") String trailertype, #RequestParam("trailernumber") int trailernumber, #RequestParam("powerunit") int powerunit) {
ModelAndView modelAndView = null;
try {
String actualFile = "";
File file = new File("file-path"); // taking file from local drive (E:/ drive)
File[] listOfFile = file.listFiles();
actualFile = listOfFile[listOfFile.length-1].getName();
System.out.println("File name : "+actualFile);
String subject = "Contact Request from "+username;
String mailBody = "Insurance copy || "+actualFile;
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setTo("example#gmail.com");
helper.setSubject(subject);
helper.setText(mailBody);
FileSystemResource attachment = new FileSystemResource(actualFile);
helper.addAttachment(actualFile, attachment);;
javaMailSender.send(mimeMessage);
System.out.println("Email sending complete.");
modelAndView = new ModelAndView(WebUrl.thankyou);
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
return modelAndView;
}
From above approach, I am getting an exception of file not found.
Failed messages: javax.mail.MessagingException: IOException while sending message;
nested exception is:
java.io.FileNotFoundException: FtlFile20220624124956.jpeg
I want to send file also with other parameters to client mail. How to do that?
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'm trying to send an email with file attachments in Spring Boot.
This is a basic gmail SMTP server application properties config:
This is my EmailService:
EmailService
When I call this method with mailMessageDto object passed, there is no exception thrown. Nothing happens, e-mail isn't sent.
I have debugged on javaMailSender.send(messsage) line of code and everything seems fine.
Update
spring.mail.properties.mail.smtp.ssl.enable=false
should be false not true spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
step 1. add dependencies in porm.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
step 2. add configuration code in application.properties
spring.mail.host=smtp.gmail.com
spring.mail.port=465
spring.mail.username=username
spring.mail.password=password
spring.mail.protocol=smtps
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=5000
spring.mail.properties.mail.smtp.writetimeout=5000
spring.mail.properties.mail.smtp.starttls.enable=true
step 3. add code in controller
masterconroller.java
#GetMapping("/sendmail")
#ResponseBody
String home() {
try {
masterServiceImpl.sendEmail("path");
return "Email Sent!";
} catch (Exception ex) {
return "Error in sending email: " + ex;
}
}
step 4. add code in MasterServiceImpl.java
#Autowired
private JavaMailSender javaMailSender;
public void sendEmail(String path) throws Exception{
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("xyz#gmail.com");
helper.setText("<html><body><h1>hello Welcome!</h1><body></html>", true);
FileSystemResource file = new FileSystemResource(new File(path));
helper.addAttachment("testfile", file);
helper.addAttachment("test.png", new ClassPathResource("test.jpeg"));
helper.setSubject("Hi");
javaMailSender.send(message);
}
I propose you to apply SRP to sendMessageWithAttachment() method by extracting functionality around adding attachments:
private void addAttachments(MailMessageDto message, MimeMessageHelper helper) {
message.getFiles().forEach(file -> addAttachment(file, helper));
}
This method streams over all files and adds every file by using addAttachment():
private void addAttachment(File file, MimeMessageHelper helper) {
String fileName = file.getName();
try {
helper.addAttachment(fileName, file);
log.debug("Added a file atachment: {}", fileName);
} catch (MessagingException ex) {
log.error("Failed to add a file atachment: {}", fileName, ex);
}
}
This will log an error for each failed attachment. Can you try this approach?
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";
}
}
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);