Enviar e-mails do Cloud Functions com tutorial do SendGrid

Neste tutorial, você aprenderá a usar o Cloud Functions para enviar e-mails por meio da plataforma SendGrid, receber dados de análise do SendGrid por meio de webhooks e carregar os dados de análise no Google BigQuery para análise.

Objetivos

  • Crie uma conta do SendGrid.
  • Escreva e implante dois Cloud Functions HTTP.
  • Escreva e implante um Background Cloud Function.
  • Envie um e-mail da função implantada via SendGrid.
  • Receba dados de análise do SendGrid via webhooks.
  • Carregue dados de análise do SendGrid no BigQuery para análise.

Custos

Este tutorial usa componentes faturáveis do Cloud Platform, inclusive:

  • Google Cloud Functions
  • Google BigQuery
  • Google Cloud Storage

Use a calculadora de preços para gerar uma estimativa de custo com base no uso previsto.

Usuários novos do Cloud Platform podem se qualificar para uma avaliação gratuita.

Antes de começar

  1. Faça login na sua Conta do Google.

    Se você ainda não tiver uma, inscreva-se.

  2. No Console do GCP, na página do seletor de projetos, selecione ou crie um projeto do GCP.

    Acesse a página do seletor de projetos

  3. Verifique se o faturamento foi ativado no projeto do Google Cloud Platform. Saiba como confirmar que o faturamento está ativado para seu projeto.

  4. Ative a(s) Cloud Functions, Cloud Storage e Google BigQuery APIs necessária(s).

    Ativar a(s) APIs

  5. Instale e inicialize o SDK do Cloud..
  6. Atualize os componentes do gcloud:
    gcloud components update
  7. Prepare seu ambiente de desenvolvimento:

Como visualizar o fluxo de dados

O fluxo de dados no aplicativo do tutorial do SendGrid envolve vários passos:

  1. Um e-mail é enviado pelo SendGrid, acionando um Cloud Function via HTTP.
  2. O SendGrid envia dados de análise para outro Cloud Function via HTTP.
  3. Os dados de análise são salvos no Cloud Storage como JSON delimitado por nova linha.
  4. Um terceiro Cloud Function é acionado por novos arquivos JSON e enfileira os arquivos JSON a serem carregados no BigQuery, onde os dados podem ser analisados.

Isso pode ajudar a visualizar os passos:

Como preparar o aplicativo

  1. Crie uma conta do SendGrid. É possível fazer isso manualmente pelo site do SendGrid ou usar o Google Cloud Launcher, que criará uma conta para você e integrará o faturamento.

    Consulte Como criar uma conta do SendGrid usando o Cloud Launcher.

  2. Crie uma chave de API do SendGrid:

    1. Faça login na sua conta do SendGrid em https://app.sendgrid.com.
    2. Navegue para Configurações > Chaves de API.
    3. Crie uma nova chave de API com acesso total.
    4. Copie a chave de API quando ela for exibida (ela será exibida somente uma vez, certifique-se de colá-la em algum lugar para poder usá-la para chamar o Cloud Function no final deste tutorial).
  3. Crie uma notificação de evento do SendGrid:

    1. Faça login na sua conta do SendGrid em https://app.sendgrid.com.
    2. Navegue para Configurações > Configurações de e-mail.
    3. Abra a guia Notificação de eventos.
    4. Clique em Editar e insira o seguinte no campo URL POST HTTP:

      Node.js 6 (obsoleto)

      https://YOUR_USERNAME:YOUR_PASSWORD@YOUR_REGION-YOUR_PROJECT_ID.cloudfunctions.net/sendgridWebhook

      em que

      • YOUR_USERNAME e YOUR_PASSWORD são um nome de usuário e uma senha de sua escolha. Insira essas duas variáveis em um arquivo de configuração em um passo subsequente.
      • YOUR_REGION é a região onde suas funções serão implantadas. Visível no seu terminal quando suas funções terminam de ser implantadas.
      • YOUR_PROJECT_ID é seu ID do projeto do Cloud. Visível no seu terminal quando suas funções terminam de ser implantadas.
    5. Selecione Tudo para relatar todas as ações de volta ao seu Cloud Function.

    6. Por fim, defina a notificação de eventos como Ativada.

  4. Crie um conjunto de dados do BigQuery usando a ferramenta de linha de comando bq do BigQuery:

    bq mk YOUR_DATASET_NAME
    

    onde YOUR_DATASET_NAME é o nome do novo conjunto de dados do BigQuery. Você também pode criar um conjunto de dados no console do BigQuery.

  5. Crie um intervalo do Cloud Storage para salvar os arquivos JSON, onde YOUR_EVENT_BUCKET_NAME é um nome de intervalo exclusivo globalmente:

    gsutil mb gs://YOUR_EVENT_BUCKET_NAME
    
  6. Clone o repositório do app de amostra na máquina local:

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

  7. Altere para o diretório que contém o código de amostra do Cloud Functions:

    Node.js 6 (obsoleto)

    cd nodejs-docs-samples/functions/sendgrid/

  8. Configure o aplicativo:

    Node.js 6 (obsoleto)

    Usando o arquivo config.default.json como modelo, crie um arquivo config.json no diretório sendgrid com o seguinte conteúdo:
    {
      "EVENT_BUCKET": "YOUR_EVENT_BUCKET_NAME",
      "DATASET": "YOUR_DATASET_NAME",
      "TABLE": "events",
      "USERNAME": "YOUR_USERNAME",
      "PASSWORD": "YOUR_PASSWORD"
    }
    • Substitua YOUR_EVENT_BUCKET_NAME pelo nome do intervalo do Cloud Storage usado para salvar arquivos JSON.
    • Substitua YOUR_DATASET_NAME pelo nome do seu conjunto de dados do BigQuery.
    • Substitua YOUR_USERNAME por um nome de usuário de sua preferência, que será usado para verificar dados provenientes do SendGrid.
    • Substitua YOUR_PASSWORD por uma senha de sua preferência, que será usada para verificar dados provenientes do SendGrid.

Como entender o código

Como importar dependências

O aplicativo importa várias dependências para se comunicar com os serviços do Google Cloud Platform:

Node.js 6 (obsoleto)

const sendgrid = require('sendgrid');
const config = require('./config.json');
const uuid = require('uuid');

// Get a reference to the Cloud Storage component
const {Storage} = require('@google-cloud/storage');
const storage = new Storage();
// Get a reference to the BigQuery component
const {BigQuery} = require('@google-cloud/bigquery');
const bigquery = new BigQuery();

Como enviar e-mails

A função a seguir cria um cliente do SendGrid para enviar e-mails:

Node.js 6 (obsoleto)

/**
 * Returns a configured SendGrid client.
 *
 * @param {string} key Your SendGrid API key.
 * @returns {object} SendGrid client.
 */
const getClient = key => {
  if (!key) {
    const error = new Error(
      'SendGrid API key not provided. Make sure you have a "sg_key" property in your request querystring'
    );
    error.code = 401;
    throw error;
  }

  // Using SendGrid's Node.js Library https://github.com/sendgrid/sendgrid-nodejs
  return sendgrid(key);
};

A função a seguir usa o cliente do SendGrid para enviar um e-mail:

Node.js 6 (obsoleto)

/**
 * Send an email using SendGrid.
 *
 * Trigger this function by making a POST request with a payload to:
 * https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/sendEmail?sg_key=[YOUR_API_KEY]
 *
 * @example
 * curl -X POST "https://us-central1.your-project-id.cloudfunctions.net/sendEmail?sg_key=your_api_key" --data '{"to":"bob@email.com","from":"alice@email.com","subject":"Hello from Sendgrid!","body":"Hello World!"}' --header "Content-Type: application/json"
 *
 * @param {object} req Cloud Function request context.
 * @param {object} req.query The parsed querystring.
 * @param {string} req.query.sg_key Your SendGrid API key.
 * @param {object} req.body The request payload.
 * @param {string} req.body.to Email address of the recipient.
 * @param {string} req.body.from Email address of the sender.
 * @param {string} req.body.subject Email subject line.
 * @param {string} req.body.body Body of the email subject line.
 * @param {object} res Cloud Function response context.
 */
exports.sendgridEmail = async (req, res) => {
  try {
    if (req.method !== 'POST') {
      const error = new Error('Only POST requests are accepted');
      error.code = 405;
      throw error;
    }

    // Get a SendGrid client
    const client = getClient(req.query.sg_key);

    // Build the SendGrid request to send email
    const request = client.emptyRequest({
      method: 'POST',
      path: '/v3/mail/send',
      body: getPayload(req.body),
    });

    // Make the request to SendGrid's API
    console.log(`Sending email to: ${req.body.to}`);
    const response = await client.API(request);

    if (response.statusCode < 200 || response.statusCode >= 400) {
      const error = Error(response.body);
      error.code = response.statusCode;
      throw error;
    }

    console.log(`Email sent to: ${req.body.to}`);

    // Forward the response back to the requester
    res.status(response.statusCode);
    if (response.headers['content-type']) {
      res.set('content-type', response.headers['content-type']);
    }
    if (response.headers['content-length']) {
      res.set('content-length', response.headers['content-length']);
    }
    if (response.body) {
      res.send(response.body);
    } else {
      res.end();
    }

    return Promise.resolve();
  } catch (err) {
    console.error(err);
    const code =
      err.code || (err.response ? err.response.statusCode : 500) || 500;
    res.status(code).send(err);
    return Promise.reject(err);
  }
};

Como receber dados analíticos

A função a seguir autenticará a solicitação de entrada do SendGrid, verificando seu nome de usuário e senha configurados:

Node.js 6 (obsoleto)

/**
 * Verify that the webhook request came from sendgrid.
 *
 * @param {string} authorization The authorization header of the request, e.g. "Basic ZmdvOhJhcg=="
 */
const verifyWebhook = authorization => {
  const basicAuth = Buffer.from(
    authorization.replace('Basic ', ''),
    'base64'
  ).toString();
  const parts = basicAuth.split(':');
  if (parts[0] !== config.USERNAME || parts[1] !== config.PASSWORD) {
    const error = new Error('Invalid credentials');
    error.code = 401;
    throw error;
  }
};

A função a seguir recebe os dados de análise do SendGrid e os salva no Cloud Storage como JSON delimitado por nova linha.

Node.js 6 (obsoleto)

/**
 * Receive a webhook from SendGrid.
 *
 * See https://sendgrid.com/docs/API_Reference/Webhooks/event.html
 *
 * @param {object} req Cloud Function request context.
 * @param {object} res Cloud Function response context.
 */
exports.sendgridWebhook = async (req, res) => {
  try {
    if (req.method !== 'POST') {
      const error = new Error('Only POST requests are accepted');
      error.code = 405;
      throw error;
    }

    verifyWebhook(req.get('authorization') || '');

    const events = req.body || [];

    // Make sure property names in the data meet BigQuery standards
    fixNames(events);

    // Generate newline-delimited JSON
    // See https://cloud.google.com/bigquery/data-formats#json_format
    const json = events.map(event => JSON.stringify(event)).join('\n');

    // Upload a new file to Cloud Storage if we have events to save
    if (json.length) {
      const bucketName = config.EVENT_BUCKET;
      const unixTimestamp = new Date().getTime() * 1000;
      const filename = `${unixTimestamp}-${uuid.v4()}.json`;
      const file = storage.bucket(bucketName).file(filename);

      console.log(`Saving events to ${filename} in bucket ${bucketName}`);

      await file.save(json);
      console.log(`JSON written to ${filename}`);
    }
    res.status(200).end();
  } catch (err) {
    console.error(err);
    res.status(err.code || 500).send(err);
    return Promise.reject(err);
  }
};

Como importar dados para o BigQuery

Por fim, a função a seguir importa os dados JSON delimitados por novas linhas para o BigQuery:

Node.js 6 (obsoleto)

/**
 * Cloud Function triggered by Cloud Storage when a file is uploaded.
 *
 * @param {object} event The Cloud Functions event.
 * @param {object} event.data A Cloud Storage file object.
 * @param {string} event.data.bucket Name of the Cloud Storage bucket.
 * @param {string} event.data.name Name of the file.
 * @param {string} [event.data.timeDeleted] Time the file was deleted if this is a deletion event.
 * @see https://cloud.google.com/storage/docs/json_api/v1/objects#resource
 */
exports.sendgridLoad = async event => {
  const file = event.data;

  if (file.resourceState === 'not_exists') {
    // This was a deletion event, we don't want to process this
    return;
  }

  try {
    if (!file.bucket) {
      throw new Error(
        'Bucket not provided. Make sure you have a "bucket" property in your request'
      );
    } else if (!file.name) {
      throw new Error(
        'Filename not provided. Make sure you have a "name" property in your request'
      );
    }

    const [table] = await getTable();

    const fileObj = storage.bucket(file.bucket).file(file.name);
    console.log(`Starting job for ${file.name}`);
    const metadata = {
      autodetect: true,
      sourceFormat: 'NEWLINE_DELIMITED_JSON',
    };
    const [job] = await table.import(fileObj, metadata);

    await job.promise();
    console.log(`Job complete for ${file.name}`);
  } catch (err) {
    console.log(`Job failed for ${file.name}`);
    return Promise.reject(err);
  }
};

Como implantar funções

  1. Para implantar a função de envio de e-mail com um acionador HTTP, execute o seguinte comando no diretório sendgrid:

    Node.js 6 (obsoleto)

  2. Para implantar a função de webhook do SendGrid com um acionador HTTP, execute o seguinte comando no diretório sendgrid:

    Node.js 6 (obsoleto)

  3. Para implantar a função que carrega dados no BigQuery com um acionador do Cloud Storage, execute o seguinte comando no diretório sendgrid:

    Node.js 6 (obsoleto)

    onde YOUR_EVENT_BUCKET_NAME é o nome do intervalo do Cloud Storage para salvar arquivos JSON.

Como enviar um e-mail

  1. Envie um e-mail com o seguinte comando:

    Node.js 6 (obsoleto)

    curl -X POST "https://YOUR_REGION-YOUR_PROJECT_ID.cloudfunctions.net/sendgridEmail?sg_key=YOUR_SENDGRID_KEY" --data '{"to":"YOUR_RECIPIENT_ADDR","from":"YOUR_SENDER_ADDR","subject":"Hello from Sendgrid!","body":"Hello World!"}' --header "Content-Type: application/json"

    em que

    • YOUR_REGION é a região onde sua função é implantada. Visível no seu terminal quando a implantação da função termina.
    • YOUR_PROJECT_ID é seu ID do projeto do Cloud. Visível no seu terminal quando a implantação da função termina.
    • YOUR_SENDGRID_KEY é a chave de API do SendGrid.
    • YOUR_RECIPIENT_ADDR é o endereço de e-mail do destinatário.
    • YOUR_SENDER_ADDR é o endereço de e-mail da sua conta do SendGrid.
  2. Verifique os registros para ter certeza de que as execuções foram concluídas:

    gcloud functions logs read --limit 100
    
  3. É possível ver os arquivos JSON salvos no intervalo do Cloud Storage especificado pelo valor EVENT_BUCKET no seu arquivo de configuração.

  4. É possível ver os dados de análise importados no BigQuery no seguinte URL:

    https://bigquery.cloud.google.com/table/YOUR_PROJECT_ID:YOUR_DATASET_NAME.events
    

    em que

    • YOUR_PROJECT_ID é o ID do projeto no Google Cloud.
    • YOUR_DATASET_NAME é o nome do conjunto de dados do BigQuery que você configurou anteriormente.

Limpeza

Para evitar que os recursos usados neste tutorial sejam cobrados na conta do Google Cloud Platform:

Como excluir o projeto

O jeito mais fácil de evitar cobranças é excluindo o projeto que você criou para o tutorial.

Para excluir o projeto:

  1. No Console do GCP, acesse a página Gerenciar recursos.

    Acessar a página Gerenciar recursos

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

Como excluir o Cloud Functions

A exclusão de Cloud Functions não remove nenhum recurso armazenado no Cloud Storage ou BigQuery.

Para excluir um Cloud Function, execute o seguinte comando:

gcloud functions delete NAME_OF_FUNCTION

onde NAME_OF_FUNCTION é o nome da função a ser excluída.

Você também pode excluir o Cloud Functions do Console do Google Cloud.