Статьи

Отправка писем с помощью Gmail JavaScript API

Эта статья была рецензирована Саймоном Кодрингтоном . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!

В предыдущей статье я продемонстрировал, как создать базовое приложение Gmail для входящих сообщений и просмотра сообщений, используя Gmail JavaScript API . Сегодня я собираюсь дополнить это приложение возможностью отправки электронной почты с помощью мощного бэкэнда Gmail. В итоге мы получим настраиваемое приложение, которое может отображать последние сообщения, разрешать составление новых сообщений и позволять нам отвечать на конкретные сообщения.

Нет необходимости читать предыдущую статью, чтобы следовать этой (хотя это даст вам более глубокое понимание предмета). Как всегда, полный исходный код этой статьи можно найти в нашем репозитории GitHub (в папке 02 - Sending mail ).

Включение Gmail API в вашей учетной записи Google

Первым шагом является настройка API Gmail в вашей учетной записи Google. Это позволит нам создавать приложения, использующие функциональность Gmail. С тех пор, как мы в последний раз рассматривали API Gmail, Google изменил пользовательский интерфейс консоли управления API — так что вот краткое обновление о том, как создать необходимые учетные данные API Gmail.

Перейдите в консоль разработчика Google и создайте проект. Нажатие кнопки « Создать» откроет нам интерфейс новой панели проекта. Отсюда нам нужно выскочить меню навигации гамбургера и выбрать API Manager , затем на боковой панели слева нам нужно выбрать Credentials , прежде чем нажать кнопку New Credentials на следующей странице.

Кнопка новых учетных данных

Теперь нам нужно создать два набора учетных данных: ключ API браузера и идентификатор клиента OAuth.

Для ключа API браузера выберите ключ API, затем ключ браузера . На следующей странице нам нужно только заполнить поле имени . Однако для производства я бы рекомендовал добавить реферер HTTP (это предотвратит злоупотребление нашим ключом API от неавторизованных доменов). Нажмите « Создать», и Google сгенерирует ключ API.

Для идентификатора клиента OAuth нажмите еще раз на Новые учетные данные и выберите Идентификатор клиента OAuth . Выберите веб-приложение в качестве типа приложения и введите хотя бы один авторизованный источник JavaScript. Для локальной среды разработки это, вероятно, будет http: // localhost или аналогичный. Нам не нужно вводить авторизованный URI перенаправления. Нажатие кнопки Create создаст идентификатор клиента и секрет клиента.

Заполнив необходимые поля, мы сможем увидеть наши учетные данные в разделе « Учетные данные ». Оставьте эту информацию открытой на вкладке браузера для следующего шага.

Начальная настройка

Возьмите копию кода

Теперь, когда учетные данные API настроены, мы должны проверить исходный код существующего демонстрационного приложения .

 git clone [email protected]:sitepoint-editors/gmail-api-javascript-example.git 

Папка 01 - Basic client содержит код из предыдущей статьи. Это то, что нас интересует.

Введите наши учетные данные

И мы должны ввести наши учетные данные в index.html :

 var clientId = 'xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com'; var apiKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; 

Настроить области аутентификации

Наконец, мы должны настроить области аутентификации . Ранее нам требовался доступ только для чтения к учетной записи пользователя Gmail. Однако отправка электронного письма требует дополнительных разрешений. Измените определение переменной scopes в index.html следующим образом (обратите внимание, что переменная scopes — это строка, разделенная пробелами):

 var scopes = 'https://www.googleapis.com/auth/gmail.readonly '+ 'https://www.googleapis.com/auth/gmail.send'; 

Как упоминалось в предыдущей статье, всегда лучше запрашивать минимальные разрешения при работе с чужими данными, особенно такими, как их учетная запись электронной почты. Эти две области — все, что нам нужно для этого приложения. Как ни странно, существует область с одноименным названием ( compose ), которая обеспечивает гораздо больший доступ, чем нам требуется.

Проверьте, что это работает

Перейдите по адресу http: // localhost / gmail-api-javascript-example (или там, где вы разместили файл index.html ). Если все идет по плану, заявка должна запросить у нас разрешение. Как только он будет авторизован, мы должны увидеть что-то вроде этого:

Gmail демонстрационное приложение в действии

Отправка электронной почты

Теперь у нас есть необходимые разрешения, которые мы можем выполнить, выполнив первый шаг — настройку пользовательского интерфейса для добавления кнопки создания. Эта кнопка будет расположена в верхнем правом углу интерфейса (в этом случае помогает pull-right класс Boostrap).

Кнопка «Создать»

 <a href="#compose-modal" data-toggle="modal" id="compose-button" class="btn btn-primary pull-right hidden">Compose</a> 

Кнопка создания не отображается в интерфейсе по умолчанию. Это так, что он появляется только после аутентификации пользователя. Чтобы включить эту функцию, нам нужно удалить hidden класс из элемента одновременно с удалением hidden класса из таблицы, в которой отображается папка «Входящие». Это означает, что мы должны изменить нашу handleAuthResult() чтобы добавить следующее сразу после loadGmailApi() :

 $('#compose-button').removeClass("hidden"); 

Кнопка Compose просто откроет модальный канал, который мы также собираемся добавить непосредственно в DOM.

 <div class="modal fade" id="compose-modal" tabindex="-1" role="dialog"> <div class="modal-dialog modal-lg"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">&times;</span> </button> <h4 class="modal-title">Compose</h4> </div> <form onsubmit="return sendEmail();"> <div class="modal-body"> <div class="form-group"> <input type="email" class="form-control" id="compose-to" placeholder="To" required /> </div> <div class="form-group"> <input type="text" class="form-control" id="compose-subject" placeholder="Subject" required /> </div> <div class="form-group"> <textarea class="form-control" id="compose-message" placeholder="Message" rows="10" required></textarea> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="submit" id="send-button" class="btn btn-primary">Send</button> </div> </form> </div> </div> </div> 

Результат должен выглядеть так:

Составить модал

Это стандартная форма в стиле Bootstrap, которая вызывает sendEmail() при sendEmail() .

 function sendEmail() { $('#send-button').addClass('disabled'); sendMessage( { 'To': $('#compose-to').val(), 'Subject': $('#compose-subject').val() }, $('#compose-message').val(), composeTidy ); return false; } 

Первое, что мы делаем при sendEmail() — отключаем кнопку отправки. Важно отключать функцию отправки формы всякий раз, когда логика отправки выполняется через Ajax, поскольку это не позволяет пользователю повторно нажимать кнопку во время выполнения запроса. Затем мы берем значения из нашей sendMessage() формы и sendMessage() все в sendMessage() . Наконец мы возвращаем false . Возвращение false из функции onsubmit важно при обработке формы через Ajax — это предотвращает onsubmit и перезагрузку страницы.

 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); } 

Эта функция предназначена для взаимодействия с API Gmail. Он принимает объект заголовков электронной почты, тело письма и функцию обратного вызова.

Мы начнем с создания сообщения электронной почты RFC 5322 (включая заголовки). В статьях предполагается, что заголовки « Date и « From необходимы для того, чтобы сообщение было действительным согласно спецификации RFC 5322. Однако я обнаружил, что эти заголовки не требуются при использовании Gmail API, поскольку Gmail автоматически добавит эти заголовки для нас. Gmail API также добавляет свой собственный Message-Id .

После того, как мы подготовили сообщение электронной почты, мы можем отправить его в API Gmail, в частности в конечную точку Users.messages: send . Здесь очень важно отметить, что мы должны указывать сообщение электронной почты внутри объекта с именем resource , а не объекта с именем message . В задокументированном примере Google на JavaScript говорится, что объект должен иметь имя message — это неверно и не будет работать. Обратите внимание, что сообщение электронной почты должно быть в кодировке base-64, для этого мы используем window.btoa () . Также обратите внимание, что реализация Google base-64 отличается от того, что предоставляют window.btoa() и window.atob() — поэтому нам необходимо выполнить некоторые замены символов после кодирования. В частности, мы должны заменить + на - и / на _ .

Наконец мы выполним запрос, передав функцию обратного вызова.

 function composeTidy() { $('#compose-modal').modal('hide'); $('#compose-to').val(''); $('#compose-subject').val(''); $('#compose-message').val(''); $('#send-button').removeClass('disabled'); } 

composeTidy() обратного вызова composeTidy() очень composeTidy() . Он просто скрывает модальный состав, очищает поля ввода и затем снова включает кнопку «Отправить».

Ответ на электронное письмо

Теперь, когда мы можем читать электронные письма и составлять новые электронные письма, следующий логический шаг — реализовать ответ на электронные письма.

Как и прежде, первое, что мы делаем, это модифицируем пользовательский интерфейс, чтобы обеспечить доступ к этой новой функциональности. Итак, мы собираемся добавить модальный нижний колонтитул к модалу представления сообщений, который мы реализовали ранее.

Кнопка ответа

 var reply_to = (getHeader(message.payload.headers, 'Reply-to') !== '' ? getHeader(message.payload.headers, 'Reply-to') : getHeader(message.payload.headers, 'From')).replace(/\"/g, '&quot;'); var reply_subject = 'Re: '+getHeader(message.payload.headers, 'Subject').replace(/\"/g, '&quot;'); $('body').append( ... '<div class="modal-footer">\ <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>\ <button type="button" class="btn btn-primary reply-button" data-dismiss="modal" data-toggle="modal" data-target="#reply-modal"\ onclick="fillInReply(\ \''+reply_to+'\', \ \''+reply_subject+'\', \ \''+getHeader(message.payload.headers, 'Message-ID')+'\'\ );"\ >Reply</button>\ </div>' ... ); 

Нижний колонтитул предлагает кнопку «Ответить», которая передает все необходимые данные (теме, идентификатору сообщения) новому модальному ответу, а затем открывает новый модальный. Параметр to требует особого внимания, поэтому он определяется перед разметкой. Мы всегда должны пытаться использовать заголовок Reply-To для параметра to , но если это не предусмотрено, то заголовка From будет достаточно. Нам также необходимо закодировать любые двойные кавычки как HTML-сущность, чтобы предотвратить разрыв нашей собственной разметки. Параметр subject требует экранирования с одинаковыми двойными кавычками и префиксом «Re:».

 function fillInReply(to, subject, message_id) { $('#reply-to').val(to); $('#reply-subject').val(subject); $('#reply-message-id').val(message_id); } 

Функция fillInReply() , которая передает поля модальному ответу, очень проста. Он просто передает полученные данные непосредственно в поля ввода модального ответа.

Модальный ответ

 <div class="modal fade" id="reply-modal" tabindex="-1" role="dialog"> <div class="modal-dialog modal-lg"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">&times;</span> </button> <h4 class="modal-title">Reply</h4> </div> <form onsubmit="return sendReply();"> <input type="hidden" id="reply-message-id" /> <div class="modal-body"> <div class="form-group"> <input type="text" class="form-control" id="reply-to" disabled /> </div> <div class="form-group"> <input type="text" class="form-control disabled" id="reply-subject" disabled /> </div> <div class="form-group"> <textarea class="form-control" id="reply-message" placeholder="Message" rows="10" required></textarea> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="submit" id="reply-button" class="btn btn-primary">Send</button> </div> </form> </div> </div> </div> 

Модальный ответ очень похож на модальный композит. Основным отличием является скрытое поле, в котором хранится идентификатор сообщения. Это необходимо для правильной работы с электронной почтой в почтовых клиентах — сопоставление темы с префиксом «Re:» недостаточно. Также мы собираемся отключить поля « Кому» и « Тема», так как они не должны изменяться в данный момент, они видны только для предоставления контекста. После sendReply() модальной формы ответа sendReply() функция sendReply() .

 function sendReply() { $('#reply-button').addClass('disabled'); sendMessage( { 'To': $('#reply-to').val(), 'Subject': $('#reply-subject').val(), 'In-Reply-To': $('#reply-message-id').val() }, $('#reply-message').val(), replyTidy ); return false; } 

Функция sendReply() во многом такая же, как sendEmail() , за исключением того, что теперь мы пропускаем заголовок In-Reply-To который позволяет почтовым клиентам правильно вести поток беседы. В документации Google говорится, что заголовок References также должен быть предоставлен, но в нашем тестировании он будет работать без него. Как только ответ отправлен, replyTidy() обратный вызов replyTidy() .

 function replyTidy() { $('#reply-modal').modal('hide'); $('#reply-message').val(''); $('#reply-button').removeClass('disabled'); } 

Опять же, это в значительной степени совпадает с нашим composeTidy() из composeTidy() . Однако на этот раз нет необходимости очищать поля ввода Subject и To, так как наша fillInReply() всегда будет перезаписывать их.

Заключительные замечания

Теперь у нас должно быть работающее приложение, которое может отображать последние сообщения, разрешать составление новых сообщений и позволять нам отвечать на конкретные сообщения.

Если вы заинтересованы в том, чтобы продолжить, есть много возможностей для улучшения с этим приложением;

  • Постепенные запросы авторизации, так что пользователь может читать свои входящие сообщения, только соглашаясь на область только для чтения. Затем, когда пользователь нажимает «Создать» или «Ответить», запрашивается другой запрос на авторизацию для области send .
  • Поле compose modeal To должно быть изменено с type="email" чтобы пользователь мог ввести комбинацию имени и адреса электронной почты (например, Jamie Shields <jamie@somewhere.com> ).
  • Поле compose modeal To должно автоматически заполняться на основе списка контактов пользователя, а также разрешать пользователю выбирать получателя непосредственно из списка.

Существует также много возможностей для добавления новых функций. Некоторые вещи, которые я хотел бы рассмотреть в будущем, включают;

  • Добавление возможности пересылки электронной почты
  • Добавление заголовков CC и BCC в электронные письма
  • Возможность просмотра полного набора заголовков, прикрепленных к электронному письму
  • Возможность отправлять электронную почту в формате HTML (с помощью редактора WYSIWYG для создания)

Если у вас есть какие-либо другие улучшения или предложения, пожалуйста, не стесняйтесь добавлять их в комментариях.

И не забывайте, полный исходный код доступен через наше GitHub репо .