Enviar correos electrónicos desde Cloud Functions con el instructivo de SendGrid

En este instructivo, se muestra cómo usar Cloud Functions para enviar correos electrónicos a través de la plataforma de SendGrid, recibir datos de estadísticas de SendGrid a través de webhooks y cargar esos datos en Google BigQuery para su análisis.

Objetivos

Costos

Este instructivo usa componentes facturables de Cloud Platform, incluidos los siguientes:

  • Google Cloud Functions
  • Google BigQuery
  • Google Cloud Storage

Usa la calculadora de precios para generar una estimación de los costos según el uso previsto.

Los usuarios nuevos de Cloud Platform pueden ser aptos para una prueba gratuita.

Antes de comenzar

  1. Sign in to your Google Account.

    If you don't already have one, sign up for a new account.

  2. Select or create a Google Cloud Platform project.

    Go to the Manage resources page

  3. Asegúrate de tener habilitada la facturación para tu proyecto.

    Aprende a habilitar la facturación

  4. Habilita las Cloud Functions, Cloud Storage y Google BigQuery API necesarias.

    Habilita las API

  5. Install and initialize the Cloud SDK.
  6. Actualiza y, luego, instala los componentes de gcloud:

    Node.js 6

    gcloud components update
  7. Prepara tu entorno de programación:

Visualiza el flujo de datos

El flujo de datos en la aplicación de instructivo de SendGrid incluye varios pasos como se muestra a continuación:

  1. Se envía un correo electrónico a través de SendGrid con solo activar una función de Cloud mediante HTTP.
  2. SendGrid envía datos de estadísticas a otra función de Cloud a través de HTTP.
  3. Se guardan datos de estadísticas en Cloud Storage como JSON delimitado por saltos de línea.
  4. Los archivos JSON nuevos activan una tercera función de Cloud y esta pone esos archivos en cola para cargarlos en BigQuery en donde se pueden analizar.

El siguiente gráfico puede ayudarte a visualizar los pasos:

Prepara la aplicación

  1. Crea una cuenta de SendGrid. Puedes hacerlo manualmente a través del sitio web de SendGrid o puedes usar Google Cloud Launcher, lo que te permitirá crear una cuenta para ti y, además, integrar la facturación.

    Consulta Crea una cuenta de SendGrid con Cloud Launcher.

  2. Crea una clave de API de SendGrid:

    1. Accede a tu cuenta de SendGrid en https://app.sendgrid.com.
    2. Navega a Configuración > Claves de API.
    3. Crea una clave de API nueva con acceso total.
    4. Copia la clave de API cuando aparezca (solo la verás una vez, por lo que debes asegurarte de pegarla en alguna parte para que puedas usarla a fin de llamar a tu función de Cloud al final de este instructivo).
  3. Crea una notificación de eventos de SendGrid:

    1. Accede a tu cuenta de SendGrid en https://app.sendgrid.com.
    2. Navega a Configuración > Configuración de correo.
    3. Abre la pestaña Notificación de eventos.
    4. Haz clic en Editar y, luego, ingresa lo siguiente en el campo HTTP POST URL:

      Node.js 6

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

      en el que:

      • YOUR_USERNAME y YOUR_PASSWORD son un nombre de usuario y contraseña de tu elección. Ingresarás estas dos variables en un archivo de configuración en un paso posterior.
      • YOUR_REGION es la región en la que se implementarán tus funciones. Esta se puede ver en la terminal cuando tus funciones terminen de implementarse.
      • YOUR_PROJECT_ID es el ID del proyecto de Cloud. Esto se podrá ver en la terminal cuando tus funciones terminen de implementarse.
    5. Selecciona Todas para volver a informar sobre todas las acciones a tu función de Cloud.

    6. Finalmente, configura la notificación de eventos en Activado.

  4. Crea un conjunto de datos de BigQuery con la herramienta de línea de comandos bq de BigQuery:

    bq mk YOUR_DATASET_NAME
    

    en el que YOUR_DATASET_NAME es el nombre del conjunto de datos de BigQuery nuevo. También puedes crear un conjunto de datos desde la consola de BigQuery.

  5. Crea un depósito de Cloud Storage para guardar los archivos JSON en el que YOUR_EVENT_BUCKET_NAME es un nombre de depósito único a nivel global:

    gsutil mb gs://YOUR_EVENT_BUCKET_NAME
    
  6. Clona el repositorio de la app de muestra en tu máquina local:

    Node.js 6

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

    De manera opcional, puedes descargar la muestra como un archivo zip y extraerla.

  7. Ve al directorio que contiene el código de muestra de Cloud Functions, como sigue:

    Node.js 6

    cd nodejs-docs-samples/functions/sendgrid/
  8. Configura la app de la siguiente forma:

    Node.js 6

    Con el archivo config.default.json como plantilla, crea un archivo config.json en el directorio sendgrid con los siguientes contenidos:
    {
    "EVENT_BUCKET": "YOUR_EVENT_BUCKET_NAME",
    "DATASET": "YOUR_DATASET_NAME",
    "TABLE": "events",
    "USERNAME": "YOUR_USERNAME",
    "PASSWORD": "YOUR_PASSWORD"
    }
    • Reemplaza YOUR_EVENT_BUCKET_NAME por el nombre del depósito de Cloud Storage que se usó para guardar los archivos JSON.
    • Reemplaza YOUR_DATASET_NAME por el nombre de tu conjunto de datos de BigQuery.
    • Reemplaza YOUR_USERNAME por un nombre de usuario de tu elección, que se usará para verificar los datos provenientes de SendGrid.
    • Reemplaza YOUR_PASSWORD por una contraseña de tu elección, que se usará para verificar los datos provenientes de SendGrid

Comprende el código

Importa dependencias

La aplicación debe importar varias dependencias con el fin de comunicarse con los servicios de Google Cloud Platform:

Node.js 6

const Buffer = require('safe-buffer').Buffer;
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();

Envía correos electrónicos

La siguiente función crea un cliente de SendGrid para enviar correos electrónicos:

Node.js 6

/**
 * Returns a configured SendGrid client.
 *
 * @param {string} key Your SendGrid API key.
 * @returns {object} SendGrid client.
 */
function 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);
}

La siguiente función usa el cliente de SendGrid para enviar un correo electrónico:

Node.js 6

/**
 * 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 = (req, res) => {
  return Promise.resolve()
    .then(() => {
      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}`);
      return client.API(request);
    })
    .then(response => {
      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();
      }
    })
    .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);
    });
};

Recibe datos de estadísticas

La siguiente función autentica la solicitud de SendGrid entrante a través de la verificación de tu nombre de usuario y contraseña configurados.

Node.js 6

/**
 * Verify that the webhook request came from sendgrid.
 *
 * @param {string} authorization The authorization header of the request, e.g. "Basic ZmdvOhJhcg=="
 */
function 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;
  }
}

La siguiente función recibe datos de estadísticas de SendGrid y guarda los datos como un archivo JSON delimitado por saltos de línea en Cloud Storage:

Node.js 6

/**
 * 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 = (req, res) => {
  return Promise.resolve()
    .then(() => {
      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}`);

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

Importa datos a BigQuery

Finalmente, esta función importa los datos del archivo JSON delimitado por saltos de línea a BigQuery:

Node.js 6

/**
 * 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 = event => {
  const file = event.data;

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

  return Promise.resolve()
    .then(() => {
      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'
        );
      }

      return getTable();
    })
    .then(([table]) => {
      const fileObj = storage.bucket(file.bucket).file(file.name);
      console.log(`Starting job for ${file.name}`);
      const metadata = {
        autodetect: true,
        sourceFormat: 'NEWLINE_DELIMITED_JSON',
      };
      return table.import(fileObj, metadata);
    })
    .then(([job]) => job.promise())
    .then(() => console.log(`Job complete for ${file.name}`))
    .catch(err => {
      console.log(`Job failed for ${file.name}`);
      return Promise.reject(err);
    });
};

Implementa las funciones

  1. Para implementar la función de envío de correos electrónicos con un activador de HTTP, ejecuta el siguiente comando en el directorio de sendgrid:

    Node.js 6

    gcloud functions deploy sendgridEmail --runtime nodejs6 --trigger-http

  2. Para implementar la función de webhook de SendGrid con un activador de HTTP, ejecuta el siguiente comando en el directorio de sendgrid:

    Node.js 6

    gcloud functions deploy sendgridWebhook --runtime nodejs6 --trigger-http

  3. Para implementar la función que carga datos en BigQuery con un activador de Cloud Storage, ejecuta el siguiente comando en el directorio de sendgrid:

    Node.js 6

    gcloud functions deploy sendgridLoad --runtime nodejs6 --trigger-bucket YOUR_EVENT_BUCKET_NAME

    en el que YOUR_EVENT_BUCKET_NAME es el nombre de tu depósito de Cloud Storage para guardar los archivos JSON.

Envía un correo electrónico

  1. Envía un correo electrónico con el siguiente comando:

    Node.js 6

    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"

    en el que:

    • YOUR_REGION es la región en la que se implementa tu función. Esta se puede ver en tu terminal cuando la función termina de implementarse.
    • YOUR_PROJECT_ID es el ID del proyecto de Cloud. Este se puede ver en tu terminal cuando la función termina de implementarse.
    • YOUR_SENDGRID_KEY es tu CLAVE DE API de SendGrid.
    • YOUR_RECIPIENT_ADDR es la dirección de correo electrónico del destinatario.
    • YOUR_SENDER_ADDR es la dirección de correo electrónico de tu cuenta de SendGrid.
  2. Revisa los registros para asegurarte de que las ejecuciones se completaron:

    gcloud functions logs read --limit 100
    
  3. Puedes ver los archivos JSON guardados en el depósito de Cloud Storage que especifica el valor EVENT_BUCKET de tu archivo de configuración.

  4. Puedes ver los datos de estadísticas importados a BigQuery en la siguiente URL:

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

    en la que:

    • YOUR_PROJECT_ID es tu ID del proyecto de Cloud.
    • YOUR_DATASET_NAME es el nombre del conjunto de datos de BigQuery que configuraste anteriormente.

Limpieza

Sigue estos pasos para evitar que se apliquen cargos a tu cuenta de Google Cloud Platform por los recursos que usaste en este instructivo:

Cómo borrar el proyecto

La manera más fácil de eliminar la facturación es borrar el proyecto que creaste para el instructivo.

Para borrar el proyecto, haz lo siguiente:

  1. In the GCP Console, go to the Projects page.

    Go to the Projects page

  2. In the project list, select the project you want to delete and click Delete .
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Borra las funciones de Cloud Functions

Borrar las funciones de Cloud Functions no quita ningún recurso almacenado en Cloud Storage o BigQuery.

Para borrar una función de Cloud, ejecuta el siguiente comando:

gcloud functions delete NAME_OF_FUNCTION

en el que NAME_OF_FUNCTION es el nombre de la función que se borrará.

También puedes borrar las funciones de Cloud Functions desde Google Cloud Platform Console.

¿Te sirvió esta página? Envíanos tu opinión:

Enviar comentarios sobre…

Documentación de Cloud Functions