Tutoriel sur l'envoi d'e-mails depuis Cloud Functions à l'aide de SendGrid

Ce tutoriel décrit l'utilisation des fonctions Cloud Functions pour envoyer des e-mails via la plate-forme SendGrid, recevoir des données d'analyse SendGrid via des webhooks et charger les données d'analyse dans Google BigQuery pour les analyser.

Objectifs

  • Créer un compte SendGrid
  • Rédiger et déployer deux fonctions Cloud Functions HTTP
  • Rédiger et déployer une fonction Cloud Functions d'arrière-plan
  • Envoyer un e-mail à partir de la fonction déployée via SendGrid
  • Recevoir des données d'analyse de SendGrid via des webhooks
  • Charger les données d'analyse SendGrid dans BigQuery pour les analyser

Coûts

Ce tutoriel fait appel à des composants facturables de Cloud Platform, en particulier :

  • Google Cloud Functions
  • Google BigQuery
  • Google Cloud Storage

Utilisez le simulateur de coût pour générer une estimation des coûts en fonction de votre utilisation prévue.

Les nouveaux utilisateurs de Cloud Platform peuvent bénéficier d'un essai gratuit.

Avant de commencer

  1. Connectez-vous à votre compte Google.

    Si vous n'en possédez pas déjà un, vous devez en créer un.

  2. Dans Cloud Console, sur la page de sélection du projet, sélectionnez ou créez un projet Cloud.

    Accéder à la page de sélection du projet

  3. Vérifiez que la facturation est activée pour votre projet Google Cloud. Découvrez comment vérifier que la facturation est activée pour votre projet.

  4. Activez les Cloud Functions, Cloud Storage, and Google BigQuery API.

    Activer les API

  5. Installez et initialisez le SDK Cloud.
  6. Mettez à jour les composants gcloud :
    gcloud components update
  7. Préparez votre environnement de développement :

Visualiser le flux de données

Le flux de données dans l'application du tutoriel SendGrid comprend plusieurs étapes :

  1. Un e-mail est envoyé via SendGrid par déclenchement d'une fonction Cloud Functions via HTTP.
  2. SendGrid envoie des données d'analyse à une autre fonction Cloud Functions via HTTP.
  3. Les données d'analyse sont enregistrées dans Cloud Storage au format JSON délimité par un retour à la ligne.
  4. Une troisième fonction Cloud Functions est déclenchée par de nouveaux fichiers JSON et met en file d'attente les fichiers JSON à charger dans BigQuery où les données peuvent être analysées.

Observez le schéma ci-dessous pour visualiser les étapes :

Préparer l'application

  1. Créez un compte SendGrid. Vous pouvez le faire manuellement via le site Web SendGrid ou utiliser Google Cloud Launcher pour créer automatiquement votre compte avec facturation intégrée.

    Consultez la section Créer un compte SendGrid en utilisant Cloud Launcher.

  2. Créez une clé API SendGrid :

    1. Connectez-vous à votre compte SendGrid sur https://app.sendgrid.com.
    2. Accédez à Paramètres > Clés API.
    3. Créez une clé API avec un accès complet.
    4. Copiez la clé API affichée (comme elle n'apparaît qu'une seule fois, veillez à la noter, car vous l'utiliserez pour appeler votre fonction Cloud Functions à la fin de ce tutoriel).
  3. Créez une notification d'événement SendGrid :

    1. Connectez-vous à votre compte SendGrid sur https://app.sendgrid.com.
    2. Accédez à Paramètres > Paramètres de messagerie.
    3. Ouvrez l'onglet Notification d'événement.
    4. Cliquez sur Modifier et entrez les informations suivantes dans le champ URL HTTP POST :

      Node.js 6 (obsolète)

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

      Où :

      • YOUR_USERNAME et YOUR_PASSWORD sont le nom d'utilisateur et le mot de passe de votre choix. Ces deux variables seront saisies dans un fichier de configuration lors d'une étape ultérieure.
      • YOUR_REGION est la région dans laquelle vos fonctions seront déployées. Cette information est visible dans votre terminal lorsque le déploiement de vos fonctions est terminé.
      • YOUR_PROJECT_ID est votre ID de projet cloud. Cette information est visible dans votre terminal lorsque le déploiement de vos fonctions est terminé.
    5. Sélectionnez Tout pour enregistrer toutes les actions dans votre fonction Cloud Functions.

    6. Enfin, définissez la notification d'événement sur Activé.

  4. Créez un ensemble de données BigQuery à l'aide de l'outil de ligne de commande bq de BigQuery :

    bq mk YOUR_DATASET_NAME
    

    YOUR_DATASET_NAME est le nom de votre nouvel ensemble de données BigQuery. Vous pouvez également créer un ensemble de données à partir de la console BigQuery.

  5. Créez un bucket Cloud Storage pour enregistrer les fichiers JSON avec un attribut de nom YOUR_EVENT_BUCKET_NAME encore jamais utilisé :

    gsutil mb gs://YOUR_EVENT_BUCKET_NAME
    
  6. Clonez le dépôt de l'exemple d'application sur votre machine locale :

    Node.js

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

    Vous pouvez également télécharger l'exemple en tant que fichier ZIP et l'extraire.

  7. Accédez au répertoire qui contient l'exemple de code de Cloud Functions :

    Node.js 6 (obsolète)

    cd nodejs-docs-samples/functions/sendgrid/

  8. Configurez l'application :

    Node.js 6 (obsolète)

    En utilisant le fichier config.default.json comme modèle, créez un fichier config.json dans le répertoire sendgrid avec le contenu suivant :
    {
      "EVENT_BUCKET": "YOUR_EVENT_BUCKET_NAME",
      "DATASET": "YOUR_DATASET_NAME",
      "TABLE": "events",
      "USERNAME": "YOUR_USERNAME",
      "PASSWORD": "YOUR_PASSWORD"
    }
    • Remplacez YOUR_EVENT_BUCKET_NAME par le nom du bucket Cloud Storage utilisé pour enregistrer les fichiers JSON.
    • Remplacez YOUR_DATASET_NAME par le nom de votre ensemble de données BigQuery.
    • Remplacez YOUR_USERNAME par un nom d'utilisateur de votre choix, qui sera utilisé pour vérifier les données provenant de SendGrid.
    • Remplacez YOUR_PASSWORD par un mot de passe de votre choix, qui sera utilisé pour vérifier les données provenant de SendGrid.

Comprendre le code

Importer des dépendances

L'application doit importer plusieurs dépendances afin de pouvoir communiquer avec les services Google Cloud Platform :

Node.js 6 (obsolète)

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

Envoyer des e-mails

La fonction suivante crée un client SendGrid pour l'envoi d'e-mails :

Node.js 6 (obsolète)

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

La fonction suivante utilise le client SendGrid pour envoyer un e-mail :

Node.js 6 (obsolète)

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

Recevoir les données d'analyse

La fonction suivante authentifie la demande SendGrid entrante en vérifiant le nom d'utilisateur et le mot de passe que vous avez configurés :

Node.js 6 (obsolète)

/**
 * 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;
  }
};

La fonction suivante reçoit les données d'analyse de SendGrid et les enregistre dans Cloud Storage au format JSON délimité par un retour à la ligne :

Node.js 6 (obsolète)

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

Importer des données dans BigQuery

Enfin, la fonction suivante importe les données JSON délimitées par un retour à la ligne dans BigQuery :

Node.js 6 (obsolète)

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

Déployer les fonctions

  1. Pour déployer la fonction d'envoi d'e-mails avec un déclencheur HTTP, exécutez la commande suivante dans le répertoire sendgrid :

    Node.js 6 (obsolète)

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

  2. Pour déployer la fonction webhook de SendGrid avec un déclencheur HTTP, exécutez la commande suivante dans le répertoire sendgrid :

    Node.js 6 (obsolète)

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

  3. Pour déployer la fonction de chargement des données dans BigQuery avec un déclencheur Cloud Storage, exécutez la commande suivante dans le répertoire sendgrid :

    Node.js 6 (obsolète)

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

    YOUR_EVENT_BUCKET_NAME est le nom de votre bucket Cloud Storage pour l'enregistrement des fichiers JSON.

Envoyer un e-mail

  1. Envoyez un e-mail avec la commande suivante :

    Node.js 6 (obsolète)

    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"

    Où :

    • YOUR_REGION est la région dans laquelle votre fonction est déployée. Cette information est visible dans votre terminal à la fin du processus de déploiement.
    • YOUR_PROJECT_ID est votre ID de projet cloud. Cette information est visible dans votre terminal à la fin du processus de déploiement.
    • YOUR_SENDGRID_KEY est votre clé API SendGrid.
    • YOUR_RECIPIENT_ADDR est l'adresse e-mail du destinataire.
    • YOUR_SENDER_ADDR est l'adresse e-mail de votre compte SendGrid.
  2. Consultez les journaux pour vous assurer que les exécutions sont terminées :

    gcloud functions logs read --limit 100
    
  3. Les fichiers JSON enregistrés dans le bucket Cloud Storage spécifiés par la valeur EVENT_BUCKET sont visibles dans votre fichier de configuration.

  4. Vous pouvez afficher les données d'analyse importées dans BigQuery à l'URL suivante :

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

    Où :

    • YOUR_PROJECT_ID correspond à votre ID de projet Google Cloud.
    • YOUR_DATASET_NAME est le nom de l'ensemble de données BigQuery que vous avez configuré précédemment.

Nettoyer

Afin d'éviter que des frais ne soient facturés sur votre compte Google Cloud Platform pour les ressources utilisées dans ce tutoriel, procédez comme suit :

Supprimer le projet

Le moyen le plus simple d'empêcher la facturation est de supprimer le projet que vous avez créé pour ce tutoriel.

Pour supprimer le projet :

  1. Dans Cloud Console, accédez à la page Gérer les ressources.

    Accéder à la page Gérer les ressources

  2. Dans la liste des projets, sélectionnez le projet que vous souhaitez supprimer, puis cliquez sur Supprimer.
  3. Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur Arrêter pour supprimer le projet.

Supprimer les fonctions cloud

La suppression de fonctions cloud ne supprime pas les ressources stockées dans Cloud Storage ou BigQuery.

Pour supprimer une fonction cloud, exécutez la commande suivante :

gcloud functions delete NAME_OF_FUNCTION

NAME_OF_FUNCTION est le nom de la fonction à supprimer.

Vous pouvez également supprimer des fonctions Cloud Functions à partir de la console Google Cloud.