Cómo acceder a Gmail desde PHP

Este post está inspirado en un caso muy interesante que me tocó resolver para un cliente.

Un poco de contexto para que se entienda de dónde viene el tema:

Una buena parte de los clientes de mi cliente llegan a través de correos que se reciben en info@...., claramente, te imaginarás que, dentro de los muchos que llegan, una parte es SPAM y la otra son contactos genuinos.

Separar la paja del trigo sería un desafío realmente interesante (Que probablemente involucraría algo de procesamiento del lenguaje natural, IA y esas cosas tan divertidas), pero… por el momento el presupuesto dio sólo para mejorar un poco el proceso de tratamiento del trigo una vez haya sido debidamente identificado.

Muy bien, entonces, el punto era que se estaba queriendo, además de re-enviar los correos útiles al equipo de ventas, subirlos de inmediato al sistema de newsletters de la empresa (Obviamente, se trataba de MailChimp).

Decidimos entonces crear una cuenta de GMail a la cual la persona encargada de identificar los correos de potenciales clientes pudiera re-enviarlos (además de al equipo de ventas) y, a partir de ahí, tener un robot que los procesara e incorporara la lista de mailing.

Técnicamente se trata de cuatro problemas:

  1. Cómo autorizar el acceso a GMail vía API
  2. Cómo ingresar a GMail y descargar los correos
  3. Cómo procesar los datos descargados para extraer las direcciones de correo
  4. Cómo incorporar esos datos a MailChimp

Sobre el último de los problemas escribí acá, así que concentrémonos en los tres primeros:null

Cómo configurar el acceso a GMail vía API

Hay una primera parte que hace a la autorización de la aplicación (Muy parecido a lo que hicimos para entrar al Google Drive):

  1. Logeate a la cuenta a la que vayas a querer acceder en forma programática
  2. Entrá al panel de administración de API
  3. Creá un nuevo proyecto
  4. Entrá al panel de administración del proyecto
  5. Habilitá la API de Gmail
  6. Creá las credenciales de acceso
    1. Seleccioná la opción «Crear ID de cliente de OAuth»
    2. Configurá la pantalla de consentimiento (No te preocupes mucho por esto, casi no lo vas a usar)
  7. Tipo de aplicación: Otro (Ponele el nombre que quieras, tampoco es importante)
  8. Si todo salió bien deberías ver una pantalla como esta:
Datos de acceso secretos
  1. Descargá el archivo json (Este es probablemente el paso más importante ya que sin él no vas a poder acceder a tus correos)

Cómo ingresar a GMail usando PHP

Bien, llegó el momento de codear 🙂

Lo más sencillo y directo es usar el SDK provisto por Google.

Para eso, nada mejor que composer:

  1. Creá un directorio para el proyecto
  2. Posicionate ahí
  3. composer init

Y cuando llegue la pregunta, incorporá como dependencia google/apiclient.

En nuestro caso, nos interesan las clases Google_Client (Necesaria para acceder a cualquier servicio de Google) y Google_Service_Gmail (Específica para el procesamiento de peticiones a Gmail).

La creación de la instancia de Google_Client requiere de la existencia de un set de credenciales válidas (que se obtienen simplemente siguiendo un enlace que generará la propia aplicación).

Una vez obtenido este objeto lo usaremos para crear nuestra instancia de la segunda clase:

$service = new Google_Service_Gmail($client);

Y a partir de ahí usaremos la propiedad users_messages para traer los correos.

Cómo procesar correos descargados de GMail usando PHP

Una vez obtenido cada uno de los correos necesitaremos procesarlos.

Lo que sabemos es que los correos vienen codificados como texto MIME, con lo cual, necesitaremos algo de ayuda para procesarlos sin volvernos locos… por ejemplo, la ayuda de PHPMimeMailParser.

Claro que no todo es tan fácil… en particular, los mails en Gmail vienen además codificados usando base64 (Pero no el base64 que viene con PHP… es un base64 codificado para URLs… Nada muy terrible, sólo hay que saberlo para actuar en consecuencia: se necesita cambiar los caracteres - y _ por + y /respectivamente. Más detalles en https://medium.com/@jrdnrc/decoding-gmail-messages-in-php-408194aeb767).

Una vez hecho esto podremos extraer alegremente las diferentes partes del correo, por ejemplo:

$from = $parser->getHeader('from');

Para conocer el remitente. O:

$subject = $parser->getHeader('Subject')

Para el asunto.

Resultado final

Ahora sí, veamos el ejemplo completo.

Como usé Composer para instalar las librerías, lo primero que voy a mostrarte es el archivo composer.json:

{
    "name": "leeway/gmail2mailchimp",
    "require": {
        "google/apiclient": "^2.0",
        "php-mime-mail-parser/php-mime-mail-parser": "^2.9"
    },
    "authors": [
        {
            "name": "Mauro Chojrin",
            "email": "mauro.chojrin@leewayweb.com"
        }
    ]
}

Ahora sí, el script que da vida a todo esto import.php:

#!/usr/bin/php -q

<?php
require_once __DIR__ . '/vendor/autoload.php';

define('APPLICATION_NAME', 'Gmail2MailChimp');
define('CREDENTIALS_PATH', __DIR__.'/gmail2mailchimp.json');
define('CLIENT_SECRET_PATH', __DIR__ . '/client_secret.json');
define('SCOPES', implode(' ', array(
        Google_Service_Gmail::MAIL_GOOGLE_COM)
));

if (php_sapi_name() != 'cli') {
    throw new Exception('Esta aplicación sólo puede ejecutarse de la terminal.');
}

echo 'Usando credenciales de: '.CREDENTIALS_PATH.PHP_EOL;
/**
 * @return Google_Client
 */
function getClient()
{
    $client = new Google_Client();
    $client->setApplicationName(APPLICATION_NAME);
    $client->setScopes(SCOPES);
    $client->setAuthConfig(CLIENT_SECRET_PATH);
    $client->setAccessType('offline');

    // Load previously authorized credentials from a file.
    $credentialsPath = expandHomeDirectory(CREDENTIALS_PATH);
    if (file_exists($credentialsPath)) {
        $accessToken = json_decode(file_get_contents($credentialsPath), true);
    } else {
        // Request authorization from the user.
        $authUrl = $client->createAuthUrl();
        printf("Open the following link in your browser:\n%s\n", $authUrl);
        print 'Enter verification code: ';
        $authCode = trim(fgets(STDIN));

        // Exchange authorization code for an access token.
        $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);

        // Store the credentials to disk.
        if (!file_exists(dirname($credentialsPath))) {
            mkdir(dirname($credentialsPath), 0700, true);
        }
        file_put_contents($credentialsPath, json_encode($accessToken));
        printf("Credentials saved to %s\n", $credentialsPath);
    }
    $client->setAccessToken($accessToken);

    // Refresh the token if it's expired.
    if ($client->isAccessTokenExpired()) {
        $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
        file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
    }
    return $client;
}

/**
 * Expands the home directory alias '~' to the full path.
 * @param string $path the path to expand.
 * @return string the expanded path.
 */
function expandHomeDirectory($path)
{
    $homeDirectory = getenv('HOME');
    if (empty($homeDirectory)) {
        $homeDirectory = getenv('HOMEDRIVE') . getenv('HOMEPATH');
    }
    return str_replace('~', realpath($homeDirectory), $path);
}


// Get the API client and construct the service object.
$client = getClient();
$service = new Google_Service_Gmail($client);

// Print the labels in the user's account.
$user = 'me';

$results = $service->users_messages->listUsersMessages($user, [ 'labelIds' => ['INBOX'] ]);

echo "Message count: " . count($results->getMessages()) . PHP_EOL;

$parser = new PhpMimeMailParser\Parser();

foreach ($results->getMessages() as $message) {
    if ($real_message = $service->users_messages->get(
        $user,
        $message->getId(),
        [
            'format' => 'raw',
        ]
    )) {
        $real_message = base64_decode(str_replace(['-', '_'], ['+', '/'], $real_message->getRaw()));
        $parser->setText($real_message);
        $from = $parser->getHeader('from');
        $body = $parser->getMessageBody();
        $matches = [];
        preg_match('/mailto:(.+)\]/', $body, $matches);
        if (count($matches) > 1) {
            $sender = $matches[1];
            echo 'Subject: "' . $parser->getHeader('Subject') . '" vino originalmente de: "' . $sender . '"' . PHP_EOL;
            echo "Subiendo a MailChimp" . PHP_EOL;
            if (syncMailchimp([
                    'email' => $sender,
                    'status' => 'subscribed',
                ])) {
                echo 'Exito!' . PHP_EOL;
                try {
                        $service->users_messages->delete(
                            'me',
                            $message->getId()
                        );
                        echo 'Eliminado del servidor!' . PHP_EOL;
                } catch (Exception $e) {
                        echo 'Error eliminando del servidor: ' . $e->getMessage() . PHP_EOL;
                }
            } else {
                echo 'Error :(' . PHP_EOL;
            }            
        }
    } else {
        echo 'No real message here...';
    }
}
echo 'Fin!' . PHP_EOL;

function syncMailchimp($data)
{
    $apiKey = 'XXXXXX';
    $listId = 'YYYYYY';

    $memberId = md5(strtolower($data['email']));
    $dataCenter = substr($apiKey, strpos($apiKey, '-') + 1);
    $url = 'https://' . $dataCenter . '.api.mailchimp.com/3.0/lists/' . $listId . '/members/' . $memberId;

    $json = json_encode([
        'email_address' => $data['email'],
        'status' => $data['status'], // "subscribed","unsubscribed","cleaned","pending"
	'source' => 'Cronjob',
    ]);

    $ch = curl_init($url);

    curl_setopt($ch, CURLOPT_USERPWD, 'user:' . $apiKey);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $json);

    $result = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    return $httpCode;
}

Y por último, la línea del crontab:

0 17 * * 1-5 /usr/bin/php import.php

Para que corra automáticamente a las 17:00 de Lunes a Viernes.

Los archivos gmail2mailchimp.jsonclient_secret.json son los descargados de Google al configurar la aplicación.

Es un poco enrevesado, pero funciona :), no está mal, ¿cierto?

mchojrin

Por mchojrin

Ayudo a desarrolladores PHP a acceder mercados y clientes más sofisticados y exigentes

3 comentarios

¿Te quedó alguna duda? Publica aca tu pregunta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.