Thursday, March 22, 2012

Sending Emails without User Interaction in Android


In this article I'm going to show you how to send an email in the background without the user even knowing - the application will do everything behind the scenes.
Before we begin, you'll need to download a few files via the link below - this is a special version of the JavaMail API, which was written specifically for Android.

I'll be walking you through a Mail wrapper that I wrote, which makes it much easier to send emails and even add attachments if that's something you'd like to do.
Here is the full wrapper class below, which I'll go through step by step - keeping in mind that you'll have to add the fore-said files if you want this to work. Add them as external libraries - they need to be accessible by the Mail class.


import java.util.Date;
import java.util.Properties;
import javax.activation.CommandMap;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.activation.MailcapCommandMap;
import javax.mail.BodyPart;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

public class Mail extends javax.mail.Authenticator {
 private String _username;
 private String _password;

 private String[] _to;
 private String _from;

 private String _port;
 private String _sport;

 private String _host;

 private String _subject;
 private String _body;

 private boolean _auth;

 private boolean _debuggable;

 private Multipart _multipart;

 public Mail() {
  _host = "smtp.gmail.com"; // default smtp server
  _port = "465"; // default smtp port
  _sport = "465"; // default socketfactory port

  _username = ""; // username
  _password = ""; // password
  _from = ""; // email sent from
  _subject = ""; // email subject
  _body = ""; // email body

  _debuggable = false; // debug mode on or off - default off
  _auth = true; // smtp authentication - default on

  _multipart = new MimeMultipart();

  // There is something wrong with MailCap, javamail can not find a
  // handler for the multipart/mixed part, so this bit needs to be added.
  MailcapCommandMap mc = (MailcapCommandMap) CommandMap
    .getDefaultCommandMap();
  mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html");
  mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");
  mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");
  mc.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
  mc.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
  CommandMap.setDefaultCommandMap(mc);
 }

 public Mail(String user, String pass) {
  this();

  _username = user;
  _password = pass;

 }

 public boolean send() throws Exception {
  Properties props = _setProperties();

  if (!_username.equals("") && !_password.equals("") && _to.length > 0
    && !_from.equals("") && !_subject.equals("")
    && !_body.equals("")) {
   Session session = Session.getInstance(props, this);

   MimeMessage msg = new MimeMessage(session);

   msg.setFrom(new InternetAddress(_from));

   InternetAddress[] addressTo = new InternetAddress[_to.length];
   for (int i = 0; i < _to.length; i++) {
    addressTo[i] = new InternetAddress(_to[i]);
   }
   msg.setRecipients(MimeMessage.RecipientType.TO, addressTo);

   msg.setSubject(_subject);
   msg.setSentDate(new Date());

   // setup message body
   BodyPart messageBodyPart = new MimeBodyPart();
   messageBodyPart.setText(_body);
   _multipart.addBodyPart(messageBodyPart);

   // Put parts in message
   msg.setContent(_multipart);

   // send email
   Transport.send(msg);

   return true;
  } else {
   return false;
  }
 }

 public void addAttachment(String filename) throws Exception {
  BodyPart messageBodyPart = new MimeBodyPart();
  DataSource source = new FileDataSource(filename);
  messageBodyPart.setDataHandler(new DataHandler(source));
  messageBodyPart.setFileName(filename);

  _multipart.addBodyPart(messageBodyPart);
 }

 @Override
 public PasswordAuthentication getPasswordAuthentication() {
  return new PasswordAuthentication(_username, _password);
 }

 private Properties _setProperties() {
  Properties props = new Properties();

  props.put("mail.smtp.host", _host);

  if (_debuggable) {
   props.put("mail.debug", "true");
  }

  if (_auth) {
   props.put("mail.smtp.auth", "true");
  }

  props.put("mail.smtp.port", _port);
  props.put("mail.smtp.socketFactory.port", _sport);
  props.put("mail.smtp.socketFactory.class",
    "javax.net.ssl.SSLSocketFactory");
  props.put("mail.smtp.socketFactory.fallback", "false");

  return props;
 }

 // the getters and setters
 public String getBody() {
  return _body;
 }

 public void setBody(String _body) {
  this._body = _body;
 }

 public void sendTo(String[] _to) {
  this._to = _to;
 }

 public void sendFrom(String _from) {
  this._from = _from;
 }
 public void setSubject(String _subject) {
  this._subject = _subject;
 }

 

 // more of the getters and setters …..
}

And now I'm going to go through each bit of code

public Mail() {
  _host = "smtp.gmail.com"; // default smtp server
  _port = "465"; // default smtp port
  _sport = "465"; // default socketfactory port

  _username = ""; // username
  _password = ""; // password
  _from = ""; // email sent from
  _subject = ""; // email subject
  _body = ""; // email body

  _debuggable = false; // debug mode on or off - default off
  _auth = true; // smtp authentication - default on

  _multipart = new MimeMultipart();

  // There is something wrong with MailCap, javamail can not find a
  // handler for the multipart/mixed part, so this bit needs to be added.
  MailcapCommandMap mc = (MailcapCommandMap) CommandMap
    .getDefaultCommandMap();
  mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html");
  mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");
  mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");
  mc.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
  mc.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
  CommandMap.setDefaultCommandMap(mc);
 }

 public Mail(String user, String pass) {
  this();

  _username = user;
  _password = pass;

 }

In this piece of code we initialise the properties, and setup the default values.
Also, we're setting up the mime types for javamail. I've also added a comment which describes why we need this.
And you've probably noticed that there are 2 constructors - one overrides the other, just incase the you want to pass the username and password when instantiating the class.

public boolean send() throws Exception {
  Properties props = _setProperties();

  if (!_username.equals("") && !_password.equals("") && _to.length > 0
    && !_from.equals("") && !_subject.equals("")
    && !_body.equals("")) {
   Session session = Session.getInstance(props, this);

   MimeMessage msg = new MimeMessage(session);

   msg.setFrom(new InternetAddress(_from));

   InternetAddress[] addressTo = new InternetAddress[_to.length];
   for (int i = 0; i < _to.length; i++) {
    addressTo[i] = new InternetAddress(_to[i]);
   }
   msg.setRecipients(MimeMessage.RecipientType.TO, addressTo);

   msg.setSubject(_subject);
   msg.setSentDate(new Date());

   // setup message body
   BodyPart messageBodyPart = new MimeBodyPart();
   messageBodyPart.setText(_body);
   _multipart.addBodyPart(messageBodyPart);

   // Put parts in message
   msg.setContent(_multipart);

   // send email
   Transport.send(msg);

   return true;
  } else {
   return false;
  }
 }

This is the most important method - here we're putting all the data from the properties and sending the mail.
public void addAttachment(String filename) throws Exception {
  BodyPart messageBodyPart = new MimeBodyPart();
  DataSource source = new FileDataSource(filename);
  messageBodyPart.setDataHandler(new DataHandler(source));
  messageBodyPart.setFileName(filename);

  _multipart.addBodyPart(messageBodyPart);
 }

You can call this method at any time if you want to add an attachment, but make sure you call it before the send method.
private Properties _setProperties() {
  Properties props = new Properties();

  props.put("mail.smtp.host", _host);

  if (_debuggable) {
   props.put("mail.debug", "true");
  }

  if (_auth) {
   props.put("mail.smtp.auth", "true");
  }

  props.put("mail.smtp.port", _port);
  props.put("mail.smtp.socketFactory.port", _sport);
  props.put("mail.smtp.socketFactory.class",
    "javax.net.ssl.SSLSocketFactory");
  props.put("mail.smtp.socketFactory.fallback", "false");

  return props;
 }


Here we're setting up the properties for the mail retrieval - defaulting to SMTP authentication. Also keep in mind that this is all defaulted to connect to the Gmail (Google) SMTP server. Below is an example of how to use the Mail wrapper, in an Android activity.
@Override
      public void onClick(View v) {
       // TODO Auto-generated method stub
       if (mEmail.getText().toString().equals(
         appPrefs.getEmail())) {
        Mail m = new Mail("email@gmail.com", "password"); 
         
             String[] toArr = {mEmail.getText().toString()}; 
             m.sendTo(toArr); 
             m.sendFrom("email@gmail.com"); 
             m.setSubject("Password"); 
             m.setBody("This is your password : " + appPrefs.getPassword()); 
        
             try { 
               m.addAttachment("/sdcard/filelocation"); 
        
               if(m.send()) { 
                 Toast.makeText(password.this, "Email was sent successfully.", Toast.LENGTH_LONG).show(); 
               } else { 
                 Toast.makeText(password.this, "Email was not sent.", Toast.LENGTH_LONG).show(); 
               } 
             } catch(Exception e) { 
               //Toast.makeText(MailApp.this, "There was a problem sending the email.", Toast.LENGTH_LONG).show(); 
               Log.e("Password", "Could not send email", e); 
             } 
        
       } else {
        
        Toast
          .makeText(
            getBaseContext(),
            "This is not that email which you enter when create account",
            Toast.LENGTH_LONG).show();
       }
      }
     });

13 comments:

Unknown said...

can you send me project because in my project NetworkOnMainThreadException error occur.My Email Address is:
patel.varun89@gmail.com

Thanks.

Unknown said...
This comment has been removed by the author.
NR said...

can you send me project because i have many probles to compile...

my email is ribasnicolas@gmail.com

Anonymous said...

I m trying to send a file and a html template through this mail...
I can successfully attach a file but unable to send the html template..

Can you tell me , how it is possible to send the Html template..

bharath simha gupta said...

can you send me project because i have many probles to compile...

my email is : gvnbsg@gmail.com

Unknown said...

My friend .. Can you send me the project? Good day!

Unknown said...

Hi all,

In this code we are stroing the password in our java file.

But here is a chance to steal the password by using de-compiler.

When we hardcode this email Id in our client code, if the end-user will be in different countries and our app will use the same email id to communicate. Due to this Gmail will force you to change the password due to account access from different IP address.

How to get ride of these security issue?
Please help me out on this?

shahir said...

hai..why there is a problem when i try to sent the email? it show the exception e..may i know what is the problem? is there any setting i need to check in my android?

Unknown said...

can you send me the project i have many errors in my project
my email id is:j.princesolomon@gmail.com

Thanks!!

Unknown said...

can you please send the me the project because i've tried it yet the app is stopping unfortunately. academic.sakshi.cs2@gmail.com

Unknown said...

1.Convert msg in MimeMessage msg = new MimeMessage(session); to be final.
2.Wrap Transport.send(msg); as new Thread(new Runnable() {
@Override
public void run() {
Transport.send(msg);
}
}).start();


after doing this ,code works fine :)

Unknown said...

thanks a lot bro....its working fiiiinnnneee...if u seen toast msg but not receiving mail in your gmail account then change your mobile device quickly... :) :)

Unknown said...

AuthenticationFailedException
12-13 15:54:26.272 8751-8971/dev.ch8n.emailer W/System.err: at javax.mail.Service.connect(Service.java:319)
12-13 15:54:26.272 8751-8971/dev.ch8n.emailer W/System.err: at javax.mail.Service.connect(Service.java:169)
12-13 15:54:26.272 8751-8971/dev.ch8n.emailer W/System.err: at javax.mail.Service.connect(Service.java:118)
12-13 15:54:26.272 8751-8971/dev.ch8n.emailer W/System.err: at javax.mail.Transport.send0(Transport.java:188)
12-13 15:54:26.272 8751-8971/dev.ch8n.emailer W/System.err: at javax.mail.Transport.send(Transport.java:118)
12-13 15:54:26.272 8751-8971/dev.ch8n.emailer W/System.err: at dev.ch8n.androidEmailer.java.GMailSender$1.run(GMailSender.java:69)
12-13 15:54:26.272 8751-8971/dev.ch8n.emailer W/System.err: at java.lang.Thread.run(Thread.java:818)