2010-05-16

App Engine email to 'admins' gotcha

I recently started adding email notifications to PicSoup (my GAEJ application). I followed the examples in the documentation and wrote a really simple function to allow me to send a simple text email to one recipient:
private void sendEmail(String recipientAddress, String recipientName, String subject, String message) {
    Properties props = new Properties();
    Session session = Session.getDefaultInstance(props, null);
    try {
        Message msg = new MimeMessage(session);
        msg.setFrom(new InternetAddress("my-admin-account@gmail.com", "PicSoup Admin"));
        msg.addRecipient(Message.RecipientType.TO, new InternetAddress(recipientAddress, recipientName));
        msg.setSubject(subject);
        msg.setText(message);
        Transport.send(msg);
        logger.info("Sent email to "+recipientAddress);
    } catch (Exception e) {
        logger.error("Failed to send email",e);
    }
}
Simple stuff, and almost identical to the documented example. Note that it uses the InternetAddress constructor that allows you to enter an address and a personal name.

This works really well and allows me to write simple calls like this:
sendEmail(p.getUser().getEmail(), p.getDisplayName(), "PicSoup info", "Your competition entry was successfully added. Good luck!");

This notifies a particular user and uses their settings to get the appropriate "personal name". I also wanted to use this call to send notifications to myself. Being the administrator I could do this using the Admins Emailed quota. To do this I thought I could use the special "admins" recipient with my sendEmail function like this:
sendEmail("admins", "PicSoup Administrators", "PicSoup info", "A new pic has been added.");

Sadly I discovered that this doesn't work. It silently fails to send the email to anyone! It turns out that this is because I have included "PicSoup Administrators" as the "personal name" in the InternetAddress object. In order to make this work I changed my sendEmail method to ignore the "personal name" for emails to admins:
private void sendEmail(String recipientAddress, String recipientName, String subject, String message) {
    Properties props = new Properties();
    Session session = Session.getDefaultInstance(props, null);
    try {
        Message msg = new MimeMessage(session);
        msg.setFrom(new InternetAddress("my-admin-account@gmail.com", "PicSoup Admin"));
        if ("admins".equals(recipientAddress)) {
            msg.addRecipient(Message.RecipientType.TO, new InternetAddress(recipientAddress));
        } else {
            msg.addRecipient(Message.RecipientType.TO, new InternetAddress(recipientAddress, recipientName));             
        }
        msg.setSubject(subject);
        msg.setText(message);
        Transport.send(msg);
        logger.info("Sent email to "+recipientAddress);
    } catch (Exception e) {
        logger.error("Failed to send email",e);
    }
}

2010-04-12

Providing for mobile web site visitors

When I'm using a mobile or an iPod Touch the web sites that detect my device and show me a mobile friendly page are more likely to be added to my favourites. For example Digg has a mobile version of their site which you are automatically taken to.

You can detect a mobile device by looking at the HTTP User Agent and Accept headers. Here are these two headers for an Android phone as captured in my application log:
INFO: Mozilla/5.0 (Linux; U; Android 1.5; en-gb; T-Mobile_G2_Touch Build/CUPCAKE) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1
INFO: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5,application/youtube-client
and for an iPod Touch:
INFO: Mozilla/5.0 (iPod; U; CPU iPhone OS 3_1_3 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7E18 Safari/528.16
INFO: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Detecting all the many and varied mobile devices out there would be a real pain without the help of MobileESP. This project supports multiple languages but I'm using Java on Google App Engine so all I needed to do was place the single java source file in my project.

I then wrote a servlet to handle requests to the home URL which redirects to the normal or mobile version of the site like so:
public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
    String userAgent = req.getHeader("User-Agent");
    String accept = req.getHeader("Accept"); 
     
    UAgentInfo info = new UAgentInfo(userAgent, accept);
       
    boolean iPhone = info.detectTierIphone();
          
    if (iPhone) {
        res.sendRedirect("/Mobile.html");
    } else {
        res.sendRedirect("/Normal.html");
    }
} 
This code simply passes the headers to the UAgentInfo object and then detects iPhone like devices. The Javadoc for detectTierIphone() says:
The quick way to detect for a tier of devices. This method detects for devices which can display iPhone-optimized web content. Includes iPhone, iPod Touch, Android, Palm WebOS, etc.

2010-03-18

Using UserPref with URL content type gadgets

Following on from my previous post I needed to add a user preference to my gadget. By following the documentation here my previous example changed to be something like this:
<?xml version="1.0" encoding="UTF-8"?>
<Module> 
      <ModulePrefs 
            height="100" 
            title="My Simple Gadget" 
            description="Test gadget" 
            author="Anonymous" 
            author_email="anonymous+gg@gmail.com"/>
      <UserPref 
            name="profileId" 
            display_name="Profile ID" 
            datatype="string" 
            urlparam="id" 
            required="true"/> 
      <Content 
            type="url" 
            href="http://yourappid.appspot.com/gadget.html" /> 
</Module>

Note that I used the urlparam attribute so that the querystring used to fetch the gadget content would be something like
http://yourappid.appspot.com/gadget.html?id=123.

My GWT code could now read this userpref like this:
public void onModuleLoad() {
    String id = Window.Location.getParameter("id");
    . . . .

Unfortunately there is a minor issue with this. It seems that some gadget containers ignore the urlparam attribute. When the gadget is hosted in the iGoogle home page it works fine but in Blogger and some other places the id parameter was not coming through in the querystring. Instead you get something like like
http://yourappid.appspot.com/gadget.html?up_profileId=123
Basically the UserPref name prefixed with up_. This meant a minor change to the GWT code:
public void onModuleLoad() {
    String id = Window.Location.getParameter("id");
    if (id == null) {
        id = Window.Location.getParameter("up_profileId"); 
    }
    . . . .

UPDATE:
It seems that Google have broken the URL type gadgets. The parameters are no longer sent in the URL to the server! See this iGoogle Developer blog post

This makes the above code rather useless. I'll be working on a new version...

UPDATE 2:
According to this forum post the problem has been fixed temporarily. I think the advice is to use HTML content type gadgets if you want to use UserPrefs. Shame.

UPDATE 3:
URL type gadgets are back for good! See this post. To summarize:
I've been talking with developers on the iGoogle team about the best way to go forward here and it looks like keeping UserPrefs in the query string for type url gadgets is the thing to do.