1

I'm trying to build a Javascript app that uses Gmail's API to send emails (to myself) including the structured data needed for Gmail's Inbox to recognise a hotel reservation. The goal is to be able to enter details about the reservation in an HTML form and have the app send me an email than Inbox then recognises as a hotel reservation and adds a Trip bundle to my inbox.

I followed a worked example, here, that uses Google Apps Scripts to send emails from my account to myself. The script pulls the body from a file as html, including the necessary structured data in JSON format. This works fine, and Inbox recognises the hotel reservation.

Here's the Apps Script code:

function manuallyCreateTrips() {
  var htmlBody = HtmlService.createHtmlOutputFromFile('hotel').getContent();

  MailApp.sendEmail({
    to: Session.getActiveUser().getEmail(),
    subject: 'Somewhere ' + new Date(),
    htmlBody: htmlBody,
  });
}

And here's the html content of the email (hotel.html):

<html>
  <body>
    <script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "LodgingReservation",
  "reservationNumber": "None",
  "reservationStatus": "http://schema.org/Confirmed",
  "underName": {
    "@type": "Person",
    "name": "Richard Guy"
  },
  "reservationFor": {
    "@type": "LodgingBusiness",
    "name": "Somewhere",
    "address": {
      "@type": "PostalAddress",
      "streetAddress": "Street",
      "addressLocality": "Locality",
      "addressRegion": "Region",
      "postalCode": "Postcode",
      "addressCountry": "UK"
    },
    "telephone": "+44 1234 123123"
  },
  "checkinDate": "2016-04-27T13:00:00+01:00",
  "checkoutDate": "2016-04-28T12:00:00+01:00"
}
</script>


    <p>
      This is a hotel reservation at Somewhere.
    </p>
  </body>
</html>

But rather than type the JSON manually, I'd like to have a prettier interface. So I've started by building a simple email client in Javascript using Gmail's API, following the worked example here. The client works OK to send emails which I then receive in my Gmail account.

Here are the functions that assemble and send the email:

function sendEmail()
{
  $('#send-button').addClass('disabled');

  sendMessage(
    {
      'To': $('#compose-to').val(),
      'Subject': $('#compose-subject').val(),
      'Content-Type': 'text/html; charset=utf-8',
    },
    $('#compose-message').val(),
    composeTidy
  );

  return false;
}

function sendMessage(headers_obj, message, callback)
{
  var email = '';

  for(var header in headers_obj)
    email += header += ": "+headers_obj[header]+"\r\n";

  email += "\r\n" + message;

  var sendRequest = gapi.client.gmail.users.messages.send({
    'userId': 'me',
    'resource': {
      'raw': window.btoa(email).replace(/\+/g, '-').replace(/\//g, '_')
    }
  });

  return sendRequest.execute(callback);
}

The function sendEmail() calls sendMessage(), passing an object containing the header lines, a string containing the (html formatted) content of the email, and a callback function to tidy up afterwards. sendMessage() combines the headers with the content and encodes the lot as base64. I send the same html content of the email as before, and the email arrives in my inbox. Any html formatting in the body text appears correctly in my email viewer. But Gmail's Inbox doesn't seem to pick up the structured data, so the reservation isn't added to a new trip.

When I compare the source of the two emails I can see significant differences in the headers.

This was the successful email:

Delivered-To: my@gmail.com
Received: by 10.79.103.71 with SMTP id b68csp826548ivc;
        Fri, 22 Apr 2016 09:09:32 -0700 (PDT)
X-Received: by 10.60.21.33 with SMTP id s1mr8900353oee.74.1461341372514;
        Fri, 22 Apr 2016 09:09:32 -0700 (PDT)
Return-Path: <3vEwaVwsJC30sjdibseuhvzhnbjm.dpnsjdibseuhvzhnbjm.dpn@maestro.bounces.google.com>
Received: from mail-ob0-x248.google.com (mail-ob0-x248.google.com. [2607:f8b0:4003:c01::248])
        by mx.google.com with ESMTPS id b134si2357523oih.30.2016.04.22.09.09.32
        for <my@gmail.com>
        (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
        Fri, 22 Apr 2016 09:09:32 -0700 (PDT)
Received-SPF: pass (google.com: domain of 3vEwaVwsJC30sjdibseuhvzhnbjm.dpnsjdibseuhvzhnbjm.dpn@maestro.bounces.google.com designates 2607:f8b0:4003:c01::248 as permitted sender) client-ip=2607:f8b0:4003:c01::248;
Authentication-Results: mx.google.com;
       dkim=pass header.i=@gmail.com;
       spf=pass (google.com: domain of 3vEwaVwsJC30sjdibseuhvzhnbjm.dpnsjdibseuhvzhnbjm.dpn@maestro.bounces.google.com designates 2607:f8b0:4003:c01::248 as permitted sender) smtp.mailfrom=3vEwaVwsJC30sjdibseuhvzhnbjm.dpnsjdibseuhvzhnbjm.dpn@maestro.bounces.google.com;
       dmarc=pass (p=NONE dis=NONE) header.from=gmail.com
Received: by mail-ob0-x248.google.com with SMTP id js7so145982383obc.0
        for <richardtguy@gmail.com>; Fri, 22 Apr 2016 09:09:32 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=gmail.com; s=20120113;
        h=mime-version:message-id:date:subject:from:to;
        bh=L6yElux8juWH24FvYKyFSYm7hSo/j0YatA7rHQ62QZ4=;
        b=iUcZ/k5ctSENTTcliXUd1jyE8FnHigru+fx97U26V4ppudjHaWF5tgTXhHD9di+qu3
         dCTG/5uXRDZq/9lXox+zLGn1CUJv8otDjzyu4zQQzMCgFWkrlPvzauPCxmWMeqKBpsEN
         sbipWbMvTPMSLbUkzWNmC7aDqHEQffdlTu69+oEidkxBVGYYGHO6XWBNT78O9owYLUXD
         +7KzpEwciGDmdXkN+bFf9kFXsIapq7kHja3o3Y56Xz/lEeZDOYfOF211IhQ/ALWKEzpe
         uL3iOu1GItLVC6oUVt46d8qYxHfNtP0qmQXzNjuL4YC/XuFeR6eJQ8mXBj8pM8YkIfst
         1GYg==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=1e100.net; s=20130820;
        h=x-gm-message-state:mime-version:message-id:date:subject:from:to;
        bh=L6yElux8juWH24FvYKyFSYm7hSo/j0YatA7rHQ62QZ4=;
        b=QOSVkBTnwapvWbnMDhcvjyvrS84JL0QsG2vbckfUMLpFgjNcA5uw0QxZYRBLgYEjkT
         r3IvezvZqgtXES6QU6XUBnZQ8h7sVhgKGvB5t/b4BZbKbnkImGAhIhSQDqFtlY0+ZgqS
         GcuWs1eacvCKMfi5RKLvH6O1Bn63gEfdYtz3EjwfZdle1lvg9WvU3GSWt8G2Hw/Bb8Z2
         sYo3Ok4jwLFgdguCsJG8CK6eUKwAdLvgmrfU1oh0UJVDVGbWallEsVJPXW/iqAYvgOVr
         0BFsTMxKheruSLeKgx5PbUYYfOul8mLbYZz/NkLxU+hHuUd+zVvIvIPmidK+0CvMyGUB
         YFeA==
X-Gm-Message-State: AOPr4FVRMkINNmOkf3rYdLZspc+99SeliqYmKfw/6w4zvXITcXqVY/CP1fJO86bNJRLEPwl2jO4UMroedpRj6A==
MIME-Version: 1.0
X-Received: by 10.182.250.201 with SMTP id ze9mr14627612obc.0.1461341372044;
 Fri, 22 Apr 2016 09:09:32 -0700 (PDT)
Message-ID: <001a11c1fabe608d4f0531150d8c@google.com>
Date: Fri, 22 Apr 2016 16:09:32 +0000
Subject: Somewhere Fri Apr 22 2016 17:09:31 GMT+0100 (BST)
From: my@gmail.com
To: my@gmail.com
Content-Type: multipart/alternative; boundary=001a11c1fabe608d370531150d89

--001a11c1fabe608d370531150d89
Content-Type: text/plain; charset=ISO-8859-1; format=flowed; delsp=yes

Somewhere

--001a11c1fabe608d370531150d89
Content-Type: text/html; charset=ISO-8859-1

<html>
  <body>
    <script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "LodgingReservation",
  "reservationNumber": "None",
  "reservationStatus": "http://schema.org/Confirmed",
  "underName": {
    "@type": "Person",
    "name": "Richard Guy"
  },
  "reservationFor": {
    "@type": "LodgingBusiness",
    "name": "Somewhere,
    "address": {
      "@type": "PostalAddress",
      "streetAddress": "Address",
      "addressLocality": "Locality",
      "addressRegion": "Region",
      "postalCode": "Postcode",
      "addressCountry": "Country"
    },
    "telephone": "+12 1234 606630"
  },
  "checkinDate": "2016-04-23T13:00:00+01:00",
  "checkoutDate": "2016-04-24T12:00:00+01:00"
}
</script>


    <p>
      Somewhere
    </p>
  </body>
</html>
--001a11c1fabe608d370531150d89--

And here's the email sent by the Javascript app using the Gmail API:

Received: from 760084270114
    named unknown
    by gmailapi.google.com
    with HTTPREST;
    Sat, 23 Apr 2016 09:43:29 -0400
To: my@gmail.com
Subject: Test reservation
Content-Type: text/html; charset=utf-8
Date: Sat, 23 Apr 2016 09:43:29 -0400
Message-Id: <CAD=04YmCHxH6mPY6FgoKLKFXd=C5sTLjQeo-9Q70jWKe4wVsRQ@mail.gmail.com>
From: my@gmail.com

<html>
  <body>
    <script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "LodgingReservation",
  "reservationNumber": "None",
  "reservationStatus": "http://schema.org/Confirmed",
  "underName": {
    "@type": "Person",
    "name": "Richard Guy"
  },
  "reservationFor": {
    "@type": "LodgingBusiness",
    "name": "Somewhere,
    "address": {
      "@type": "PostalAddress",
      "streetAddress": "Address,
      "addressLocality": "Locality",
      "addressRegion": "Region",
      "postalCode": "Postcode",
      "addressCountry": "UK"
    },
    "telephone": "+12 3456 606630"
  },
  "checkinDate": "2016-04-23T13:00:00+01:00",
  "checkoutDate": "2016-04-24T12:00:00+01:00"
}
</script>


    <p>
      Somewhere
    </p>
  </body>
</html>

Any ideas??

  • Looking at the headers, the working one says `charset=ISO-8859-1`, whereas the one you insert in the non-working one is utf8. Does changing that make a difference? – Frederik Deweerdt Apr 23 '16 at 14:09
  • Unfortunately not. I suspect the problem is that Inbox ignores structured data [unless the email is authenticated using DKIM](https://developers.google.com/gmail/markup/actions/securing-actions). I can't work out how to enable authentication using the Gmail API. In the meantime as the Apps Script function `MailApp.sendEmail()` does send authenticated emails, I'll have a go at using Google Forms or Sheets to get the input data and use an Apps Script to send the email. – Richard Guy Apr 24 '16 at 18:47
  • @frederik: Thanks. I worked around this by inputting the data to a Google Sheets document and using an Apps Script to pull out the data, construct the html body for the email and send it using MailApp.sendEmail(). The interface isn't pretty, but it wasn't too tricky to add a "Create Trip" button to the sheet. Inbox recognises this correctly as a trip, as long as the content is properly structured. Still curious to know if I could have done this with the Gmail API... – Richard Guy Apr 24 '16 at 21:15
  • Have you checked [Authorizing Your App with Gmail](https://developers.google.com/gmail/api/auth/about-auth)? Hope it helps. :) – Teyam Apr 25 '16 at 16:41
  • @Teyam: Thanks, but I'm pretty sure the app is correct authorised, as I'm able to send emails successfully. The problem is that Inbox ignores the structured data when it receives them. I've managed to achieve my original intent using Google Apps Script, as described in my answer below. – Richard Guy Apr 26 '16 at 18:06

0 Answers0