Advertisement
  1. Code
  2. Email

Construyendo Características Avanzadas de Correo Electrónico Con IMAP y PHP

Scroll to top
Read Time: 17 min

Spanish (Español) translation by Rafael Chavarría (you can also view the original English article)

What You'll Be Creating

En este tutorial, te guiaré a través de algunos ejemplos del mundo real de cómo puedes usar PHP e IMAP para construir nuevas características para administrar tu correo electrónico---características que los grandes proveedores de correo electrónico no han construido para nosotros.

Mi interés en esto comenzó en 2010 cuando escribí Doce Ideas de Gmail para Revolucionar el Correo Electrónico (De nuevo), pero la mayoría de las ideas que deseaba tener permanecieron fuera de alcance. Por tan importante que es el correo electrónico, la innovación del correo electrónico como aplicación ha sido bastante lenta.

Nos estamos ahogando en correo electrónico, y administrar nuestras bandejas de entrada aún es una pesada carga. Los servicios de correo y clientes han hecho muy poco para ayudarnos con esto. La mayoría del correo electrónico que recibimos es enviado por máquinas, no personas, y aún así somo los que tenemos que procesar de manera individual todo eso.

Un análisis de mi propio correo electrónico mostró que estaba recibiendo correo electrónico de más de 230 remitentes automáticos, mucho menos personas reales. Estaba cansado de construir filtros en Gmail y llenando una miriada de formularios de cancelación de suscripción. Quería tener más control sobre administrar mi correo electrónico y simplificar mi vida.

Finalmente, este año anterior, decidí construir las características que necesitaba. El resultado es Simplify Email (SE), una pequeña aplicación web que puedes hospedar tu mismo la cuál ofrece una variedad de geniales nuevas características de correo electrónico que puedes revisar todas en el sitio web del proyecto.

La cosa más genial sobre SE es que es una plataforma para leer, analizar, enrutar y administrar tu correo electrónico---las posibilidades abundan. Simplify Email es esencialmente un patio de recreo programable para "hackear" tu propio correo electrónico.

Te guiaré a través del código de tres ejemplos de SE que usan PHP, IMAP y MySQL para trabajar con correo electrónico:

  1. Revisar tu bandeja de entrada y filtrar mensajes
  2. Implementar un reto de Lista blanca para remitentes desconocidos
  3. Reportar correo electrónico sin responder

Este tutorial definitivamente te dará un buen comienzo para escribir código IMAP en PHP. Pero también puedes trabajar directamente con el código base de Simplify Email. Puedes comprar el código por tan poco como $10, y aquí está una versión más antigua de código abierto (la cuál carece de otras características que describimos abajo). Se proporcionan guías de instalación para configuraciones Linux típicas. También ofrezco imágenes pre-instaladas en Digital Ocean por $25 así como una instalación hanheld valet. SE está escrito en PHP, en el Framework Yii.

Nota que no podrás acceder a la mayoría de servidores de correo electrónico por medio de tu máquina local de desarrollo a menos que compiles una librería IMAP segura para PHP. Esta es una de las razones por las que aliento a la gente a ejecutar SImplify Email en droplets en Digital Ocean. También hay unos cuántos trucos para hacer que la seguridad de cuenta Google te deje entrar vía IMAP.

Trabajando con IMAP

Cómo Funciona Simplify Email

Con SE, puedes continuar usando tu cliente elegido de correo electrónico en Web y tus dispositivos móviles. No tienes que cambiar ninguna aplicación o hábitos personales. SE accede a tus cuentas de correo electrónico tras bambalinas vía IMAP; actuando como un asistente personal inteligente, SE pre-procesa tu correo electrónico, moviendo mensajes a los lugares apropiados basado en todo lo que le has dicho.

Cuando un mensaje llega de un remitente familiar, SE lo mueve a la carpeta que hayas especificado. Cuando uno llega de un remitente desconocido por primera vez, este lo mueve a la carpeta de revisión.

Cada par de horas (o a una frecuencia de tu elección), SE te enviará un resumen de a dónde movió tus mensajes y qué mensajes están en revisión. Nota, los enlaces para entrenar remitentes están incluidos para la carpeta de revisión, haciendo bastante sencillo entrenar a SE con el tiempo.

The Simplify Email Message Digest Shows You What Came In and How It Was FilteredThe Simplify Email Message Digest Shows You What Came In and How It Was FilteredThe Simplify Email Message Digest Shows You What Came In and How It Was Filtered

En cualquier momento, puedes navegar tu carpeta de revisión---no tienes que esperar para que el resumen llegue. Pero la ventaja de SE es que ya no necesitas navegar tus carpetas; puedes solo leer tu resumen para obtener una vista del correo electrónico que has recibido y entrenar nuevos remitentes.

1. Revisando Tu Bandeja de Entrada y Filtrando Mensajes

SE usa varias tareas cron para operar en segundo plano en tu servidor. Cada una llamada desde DaemonController.php.

El primero, processInbox, es llamado frecuentemente y necesita operar rápidamente---su trabajo es filtrar correo electrónico y sacarlo de la bandeja de entrada tan rápido como sea posible y a la carpeta de triaje, llamada la carpeta de filtrado.

El segundo, processFiltering, es más intenso en el procesamiento y realiza operaciones más profundas en correo electrónico, moviendo finalmente mensajes a su destino final.

El Método ProcessInbox

Las tareas cron llaman a processInbox de manera regular:

1
public function actionInbox() {
2
    
3
    // moves inbox messages to @filtering

4
    // runs frequently 

5
    $r = new Remote();
6
    $r->processInbox();    
7
    
8
}
The Simplify Email Accounts Configuration PageThe Simplify Email Accounts Configuration PageThe Simplify Email Accounts Configuration Page

Para cada cuenta, desencriptamos tus credenciales de correo electrónico y después usamos imap_open para crear un flujo IMAP a tu carpeta de bandeja de entrada:

1
public function open($account_id, $mailbox='',$options=NULL) {
2
  // opens folder in an IMAP account

3
  $account = Account::model()->findByPk($account_id);
4
  $this->hostname = $account->address;
5
  if (!stristr($this->hostname,'{'))
6
    $this->hostname = '{'.$this->hostname.'}';
7
  $cred = Account::model()->getCredentials($account->cred);
8
  if ($account->provider == Account::PROVIDER_ICLOUD) {
9
    // icloud accepts only name part of mailbox e.g. stevejobs vs. stevejobs@icloud.com

10
    $temp = explode('@',$cred[0]);
11
    $cred[0]=$temp[0];
12
  }
13
  $this->stream = imap_open($this->hostname.$mailbox,$cred[0],$cred[1],$options,1) or die('Cannot connect to mail server - account_id:'.$account_id .' '.print_r(imap_errors()));
14
}

Dentro de processInbox, usamos las funciones de la librería PHP imap_search y imap_fetch_overview para recuperar un arreglo de mensajes:

1
// lookup folder_id of this account's INBOX

2
$folder_id = Folder::model()->lookup($account_id,$this->path_inbox);
3
$this->open($account_id,$this->path_inbox);
4
$cnt=0;
5
$message_limit= 50; // break after n messages to prevent timeout

6
echo 'Sort since: '.date("j F Y",$tstamp);           
7
// imap_search date format 30 November 2013

8
 $recent_messages = @imap_search($this->stream, 'SINCE "'.date("j F Y",$tstamp).'"',SE_UID); 
9
 if ($recent_messages===false) continue; // to do - continue into next account

10
 $result = imap_fetch_overview($this->stream, implode(',',array_slice($recent_messages,0,$message_limit)),FT_UID);

Después procesamos el arreglo de mensajes en la bandeja de entrada:

1
foreach ($result as $item) {         
2
   if (!$this->checkExecutionTime($time_start)) break;
3
   // get msg header and stream uid

4
   $msg = $this->parseHeader($item); 

Aquí está una versión adaptada del código de análisis de encabezado IMAP públicamente disponible que reúne la información adicional que SE necesita para una variedad de tareas. Básicamente, usa imap_rfc822_parse_adrlist para determinar información de remitente, id de mensaje, asunto y estampas de tiempo (o información de remitente cuando se escanea la carpeta de enviados):

1
  public function parseHeader($header) {
2
    // parses header object returned from imap_fetch_overview    

3
    if (!isset($header->from)) {
4
      return false;
5
    } else {
6
      $from_arr = imap_rfc822_parse_adrlist($header->from,'gmail.com');
7
      $fi = $from_arr[0];
8
      $msg = array(
9
        "uid" => (isset($header->uid))
10
              ? $header->uid : 0,
11
         "personal" => (isset($fi->personal))
12
            ? @imap_utf8($fi->personal) : "",
13
          "email" => (isset($fi->mailbox) && isset($fi->host))
14
              ? $fi->mailbox . "@" . $fi->host : "",
15
          "mailbox" => (isset($fi->mailbox))
16
            ? $fi->mailbox : "",
17
          "host" => (isset($fi->host))
18
            ? $fi->host : "",
19
          "subject" => (isset($header->subject))
20
              ? @imap_utf8($header->subject) : "",
21
          "message_id" => (isset($header->message_id))
22
                ? $header->message_id : "",
23
          "in_reply_to" => (isset($header->in_reply_to))
24
                    ? $header->in_reply_to : "",
25
          "udate" => (isset($header->udate))
26
              ? $header->udate : 0,
27
          "date_str" => (isset($header->date))
28
              ? $header->date : ""
29
      );    
30
      // handles fetch with uid and rfc header parsing

31
      if ($msg['udate']==0 && isset($header->date)) {
32
          $msg['udate']=strtotime($header->date);
33
      }
34
      $msg['rx_email']='';        
35
      $msg['rx_personal']='';
36
        $msg['rx_mailbox']='';
37
        $msg['rx_host']='';
38
      if (isset($header->to)) {        
39
        $to_arr = imap_rfc822_parse_adrlist($header->to,'gmail.com');
40
        $to_info = $to_arr[0];
41
        if (isset($to_info->mailbox) && isset($to_info->host)) {
42
          $msg['rx_email']=$to_info->mailbox.'@'.$to_info->host;
43
        }
44
        if (isset($to_info->personal))
45
          $msg['rx_personal']=$to_info->personal;
46
        if (isset($to_info->mailbox))
47
          $msg['rx_mailbox']=$to_info->mailbox;
48
        if (isset($to_info->host))
49
          $msg['rx_host']=$to_info->host;
50
      }
51
      return $msg;
52
    }
53
  }

Creamos registros para el remitente y el sobre de mensaje dentro de nuestra base de datos:

1
   // skip any system messages

2
   if ($msg['email']==$system_email) continue;
3
    // if udate is too old, skip msg

4
    if (time()-$msg['udate']>$this->scan_seconds) continue; // skip msg

5
    // default action

6
     $action = self::ACTION_MOVE_FILTERED;
7
     $isNew = $s->isNew($account_id,$msg["email"]);
8
    // look up sender, if new, create them

9
    $sender_id = $s->add($user_id,$account_id,$msg["personal"], $msg["mailbox"], $msg["host"],0);                       
10
    $sender = Sender::model()->findByPk($sender_id);
11
     // create a message in db if needed

12
     $message_id = $m->add($user_id,$account_id,0,$sender_id,$msg['message_id'],$msg['subject'],$msg['udate'],$msg['in_reply_to']);        
13
        $message = Message::model()->findByPk($message_id);

Si el remitente es nuevo para nosotros (desconocido), enviaremos un correo electrónico de reto de lista blanca (hablaremos más sobre los retos de lista blanca en la siguiente sección de abajo):

1
if ($isNew) {
2
        $this->challengeSender($user_id,$account_id,$sender,$message);
3
      }

Después, determinamos si el usuario pudiera haber arrastrado un mensaje de otra carpeta de vuelta a la bandeja de entrada---pretendiendo entrenarla vía arrastrar y soltar. Si es así, establecemos el entrenamiento para este remitente a la bandeja de entrada. En otras palabras, la siguiente vez, quisiéramos solo enrutar mensajes de este remitente a la bandeja de entrada:

1
         if ($message['status'] == Message::STATUS_FILTERED ||
2
   	    $message['status'] == Message::STATUS_REVIEW ||
3
   	    ($message['status'] == Message::STATUS_TRAINED && $message['folder_id'] <> $folder_id) ||
4
   	    ($message['status'] == Message::STATUS_ROUTED && $message['folder_id'] <> $folder_id))
5
   	    {
6
     	  // then it's a training

7
   	    $action = self::ACTION_TRAIN_INBOX;     	    
8
   	  } else if (($message['status'] == Message::STATUS_TRAINED || $message['status'] == Message::STATUS_ROUTED) && $message['folder_id'] == $folder_id) {
9
   	    // if trained already or routed to inbox already, skip it

10
   	    $action = self::ACTION_SKIP;  
11
   	    echo 'Trained previously, skip ';lb();   	  
12
   	    continue;  
13
   	  }

Si no, nos prepararemos para mover el mensaje a la carpeta de Filtrado para futuro procesamiento. Primero, deberíamos enviar notificaciones al teléfono del usuario si hay una coincidencia de remitente o palabra clave para notificaciones (y no son horas tranquilas):

1
   if ($action == self::ACTION_MOVE_FILTERED) {
2
     $cnt+=1;         
3
     if ($sender->exclude_quiet_hours == Sender::EQH_YES or !$this->isQuietHours($user_id)) {
4
       // send smartphone notifications based on sender

5
       if ($sender->alert==Sender::ALERT_YES) {
6
         $this->notify($sender,$message,Monitor::NOTIFY_SENDER);
7
       }
8
       // send notifications based on keywords

9
       if (AlertKeyword::model()->scan($msg)) {
10
         $this->notify($sender,$message,Monitor::NOTIFY_KEYWORD);
11
       }               
12
     }               
13
     // move imap msg to +Filtering

14
     echo 'Moving to +Filtering';lb();
15
     //$result = @imap_mail_move($this->stream,$msg['uid'],$this->path_filtering,CP_UID);

16
     $result = $this->messageMoveHandler($msg['uid'],$this->path_filtering,false);             
17
     if ($result) {
18
       echo 'moved<br />';
19
       $m->setStatus($message_id,Message::STATUS_FILTERED);
20
     }
21
   } 

Si el mensaje fue arrastrado a la bandeja de entrada, entonces actualizaremos nuestros ajustes de entrenamiento:

1
else if ($action == self::ACTION_TRAIN_INBOX) {
2
     // set sender folder_id to inbox

3
     echo 'Train to Inbox';lb();
4
     $m->setStatus($message_id,Message::STATUS_TRAINED);
5
     // only train sender when message is newer than last setting

6
     if ($msg['udate']>=$sender['last_trained']) {
7
       $s->setFolder($sender_id,$folder_id);
8
     } 
9
   }

El Método ProcessFiltering

El método de procesamiento secundario es llamado processFiltering, también en DaemonController.php. Este hace los aspectos que consumen más tiempo de mover mensajes a las carpetas apropiadas:

1
public function actionIndex()
2
{
3
  // processes messages in @Filtering to appropriate folders

4
  $r = new Remote();
5
  $r->processFiltering();
6
  // Record timestamp of cronjob for monitoring

7
  $file = file_put_contents('./protected/runtime/cronstamp.txt',time(),FILE_USE_INCLUDE_PATH);	  
8
}

Este método abre tu cuenta de correo electrónico para buscar mensajes recientes y reunir información acerca de estos. También usa imap_searchimap_fetch_overview y parseHeader:

1
$tstamp = time()-(7*24*60*60); // 7 days ago

2
$recent_messages = @imap_search($this->stream, 'SINCE "'.date("j F Y",$tstamp).'"',SE_UID);
3
if ($recent_messages===false) continue; // to do - continue into next account

4
$result = imap_fetch_overview($this->stream, implode(',',array_slice($recent_messages,0,$message_limit)),FT_UID);
5
foreach ($result as $item) {         
6
      $cnt+=1;
7
      if (!$this->checkExecutionTime($time_start)) break;
8
      // get msg header and stream uid

9
      $msg = $this->parseHeader($item);            

El ciclo primario de procesamiento para cada mensaje en la carpeta de filtrado está bastante detallado. Primero vemos la dirección de receptor, ej. mensajes para el dominio happyvegetarian.com van a la carpeta veggie:

1
 // Set the default action to move to the review folder

2
 $action = self::ACTION_MOVE_REVIEW;
3
 $destination_folder =0;
4
 // look up & create recipient

5
 $recipient_id = $r->add($user_id,$account_id,$msg['rx_email'],0);
6
 $routeByRx = $this->routeByRecipient($recipient_id);
7
 if ($routeByRx!==false) {
8
  $action = $routeByRx->action;
9
  $destination_folder = $routeByRx->destination_folder;
10
 }            

Después buscamos al remitente y creamos un nuevo registro en la base de datos (si es necesario). Si existe entrenamiento para el remitente, podemos establecer la carpeta de destino:

1
 // look up sender, if new, create them

2
 $sender_id = $s->add($user_id,$account_id,$msg["personal"], $msg["mailbox"], $msg["host"],0);                       
3
 $sender = Sender::model()->findByPk($sender_id);
4
 // if sender destination known, route to folder

5
 if ($destination_folder ==0 && $sender['folder_id'] > 0) {
6
   $action = self::ACTION_ROUTE_FOLDER;  
7
   $destination_folder = $sender['folder_id'];      
8
 }
9
 

Si un remitente no entrenado (nuevo) se ha verificado a sí mismo vía un reto de Lista Blanca (que discutiré en la siguiente sección de abajo), entonces enrutaremos este mensaje a la bandeja de entrada:

1
// whitelist verified senders go to inbox

2
 if ($sender->is_verified==1 && $sender['folder_id'] ==0 && UserSetting::model()->useWhitelisting($user_id)) {
3
   // place message in inbox

4
   $action = self::ACTION_ROUTE_FOLDER;  
5
   $destination_folder = Folder::model()->lookup($account_id,$this->path_inbox);             
6
 }

Después, creamos una entrada de mensaje en la base de datos con la información de sobre acerca de este mensaje:

1
  // create a message in db

2
      $message = Message::model()->findByAttributes(array('message_id'=>$msg['message_id']));
3
  if (!empty($message)) {
4
    // message exists already, 

5
	  $message_id = $message->id;    	  
6
  } else {
7
    $message_id = $m->add($user_id,$account_id,0,$sender_id,$msg['message_id'],$msg['subject'],$msg['udate'],$msg['in_reply_to']);         
8
  }

Si es de un remitente desconocido sin verificar, podemos mover el mensaje a la carpeta de revisión. La carpeta de revisión contiene todos los mensajes de remitentes que no reconocemos.

Si el mensaje es de un remitente desconocido y tenemos un destino en mente, podemos moverlo mientras no sean horas tranquilas (y no molestar esté apagado):

1
  if ($recipient_id!==false) $m->setRecipient($message_id,$recipient_id);
2
  if ($action == self::ACTION_MOVE_REVIEW) {
3
    echo 'Moving to +Filtering/Review';lb();
4
    //$result = @imap_mail_move($this->stream,$msg['uid'],$this->path_review,CP_UID);

5
    $result = $this->messageMoveHandler($msg['uid'],$this->path_review,false);               
6
    if ($result) {
7
      echo 'moved<br />';
8
      $m->setStatus($message_id,Message::STATUS_REVIEW);
9
    }      
10
 } else if ($action == self::ACTION_ROUTE_FOLDER || $action == self::ACTION_ROUTE_FOLDER_BY_RX) {
11
  // lookup folder name by folder_id

12
  $folder = Folder::model()->findByPk($destination_folder);       
13
  // if inbox & quiet hours, don't route right now

14
  if (strtolower($folder['name'])=='inbox' and $sender->exclude_quiet_hours == Sender::EQH_NO and $this->isQuietHours($user_id)) continue;
15
  echo 'Moving to '.$folder['name'];lb();
16
  $mark_read = Folder::model()->isMarkRead($folder['mark_read']) || Sender::model()->isMarkRead($sender['mark_read']);
17
  //$result = @imap_mail_move($this->stream,$msg['uid'],$folder['name'],CP_UID);

18
  $result = $this->messageMoveHandler($msg['uid'],$folder['name'],$mark_read);
19
  if ($result) {
20
    echo 'moved<br />';
21
    $m->setStatus($message_id,Message::STATUS_ROUTED);         
22
    $m->setFolder($message_id,$destination_folder);
23
  }
24
}

Durante las horas tranquilas, los mensajes son mantenidos primariamente en la carpeta de filtrado.

Cada par de horas, un proceso diferente construirá el resumen de mensaje usando los registros de la tabla de mensaje para determinar cuáles correos electrónicos fueron recientemente recibidos y filtrados y cómo fueron enrutados.

2. Implementando un Reto de Lista Blanca para Remitentes Desconocidos

La meta del reto de lista blanca es mantener cualquier mensaje de un remitente desconocido, ej. posiblemente un robot de mercadeo o spammer, fuera de tu bandeja de entrada. SE coloca correo electrónico de remitentes desconocidos en la carpeta de revisión. Sin embargo, si enciendes el listado blanco, enviamos un correo de reto que le da al remitente una oportunidad para verificar que son humanos. Si ellos responde, moveremos el mensaje a tu bandeja de entrada. Si el correo electrónico resulta ser no solicitado, puedes borrar el mensaje del resumen o arrastrarlo a cualquier carpeta a la que lo quieres entrenar.

El usuario puede encender y apagar el listado blanco en los ajustes:

Simplify email settings for whitelistingSimplify email settings for whitelistingSimplify email settings for whitelisting

Para implementar listado blanco, enviamos retos de correo electrónico siempre que nuevo correo llega de un nuevo remitente:

1
if ($isNew) {
2
        $this->challengeSender($user_id,$account_id,$sender,$message);
3
      }

ChallengeSender envía un enlace codificado al usuario para que le den clic. También tenemos algunas protecciones para asegurar que no nos atrapamos a nosotros mismos en un ciclo de correo con un mensaje de fuera de la oficina:

1
 public function challengeSender($user_id,$account_id,$sender,$message) {
2
   // whitelist email challenge

3
   $yg = new Yiigun();
4
   $ac = Account::model()->findByPk($account_id);
5
   if (!empty($ac['challenge_name']))
6
     $from = $ac['challenge_name'].' <no-reply@'.$yg->mg_domain.'>';
7
    else
8
      $from = 'Filter <no-reply@'.$yg->mg_domain.'>';      
9
   $cred = Account::model()->getCredentials($ac->cred);
10
   $account_email = $cred[0];
11
   unset($cred); 
12
   // safety: checks no recent email

13
   if ($sender->last_emailed>(time()-(48*60*60))) return false;   
14
   if ($sender->isBot($sender['email'])) {
15
     // to do - can also set this person to bulk by default

16
     return false;
17
   }
18
$link=Yii::app()->getBaseUrl(true)."/sender/verify/s/".$sender->id."/m/".$message->id.'/u/'.$message->udate;
19
    $subject = 'Please verify the message you sent to '.$account_email;
20
    $body="<p>Hi,<br /><br /> I'm trying to reduce unsolicited email. Could you please verify your email address by clicking the link below:<br /><a href=\"".$link.'">'.$link.'</a><br /><br />Verifying your email address will help speed your message into my inbox. Thanks for your assistance!</p>';
21
    $yg->send_html_message($from, $sender['email'], $subject,$body);
22
    // update last_emailed

23
    $sender->touchLastEmailed($sender->id);
24
 }

Después, si el receptor da clic en el enlace codificado, lo verificamos en la base de datos. El Controlador de Envío procesa estas peticiones y revisa su validez:

1
  public function actionVerify($s = 0, $m=0,$u=0) {
2
    // verify that secure msg url from digest is valid, log in user, show msg

3
    $sender_id = $s;
4
    $message_id = $m;
5
    $udate = $u;
6
    $msg = Message::model()->findByPk($message_id);
7
    if (!empty($msg) && $msg->sender_id == $sender_id && $msg->udate == $udate) {
8
      $result = 'Thank you for your assistance. I\'ll respond to your email as soon as possible.';
9
      $a = new Advanced();
10
      $a->verifySender($msg->account_id,$sender_id);
11
    } else {
12
      $result = 'Sorry, we could not verify your email address.';
13
    }
14
    	$this->render('verify',array(
15
			'result'=>$result,
16
		));
17
  }

Esto le dice a nuestros ciclos de procesamiento que mueva este y futuros mensajes de este remitente a la bandeja de entrada.

3. Reportando Correo Electrónico Sin Responder

Algunas veces ayuda ver un resumen de mensajes que has enviado pero no recibido una respuesta a estos. Para identificar estos, Simplify Email monitorea mensajes que han sido enviados pero no han recibido una respuesta.

Cada mensaje que recibimos contiene un id único, llamado el message_id (parte de la especificación IMAP). Frecuentemente luce como esto:

1
Message-Id: <CALe0OAaF3fb3d=gCq2Fs=Ex61Qp6FdbiA4Mvs6kTQ@mail.gmail.com>

Además, cuando los mensajes son enviados en respuesta a otros mensajes, estos tienen un campo in_reply_to que enlaza de vuelta al message_id original.

Así que, usamos una consulta SQL para encontrar todos los mensajes recibidos que no tienen un mensaje de respuesta correspondiente referenciando su message_id. Para eso, usamos un LEFT OUTER JOIN en donde no hay id in_reply_to:

1
public function getUnanswered($account_id,$mode=0, $range_days = 7) {
2
    if ($mode==0)
3
      $subject_compare = 'not';
4
    else
5
      $subject_compare = '';
6
    $query = Yii::app()->db->createCommand("SELECT fi_sent_message.id, fi_sent_message.recipient_id as sender_id,fi_sent_message.subject,fi_sent_message.udate,fi_message.in_reply_to,fi_sent_message.message_id  FROM fi_sent_message LEFT OUTER JOIN fi_message ON fi_message.in_reply_to = fi_sent_message.message_id WHERE fi_sent_message.account_id = ".$account_id." AND fi_message.in_reply_to is null and fi_sent_message.udate > ".(time()-(3600*24*$range_days))." and fi_sent_message.subject ".$subject_compare." like 'Re: %' ORDER BY fi_sent_message.udate  DESC")->queryAll();
7
    return $query;
8
  }

Usamos el modo $subject_compare para diferenciar entre mensajes enviados que no han sido contestados y nuestras respuestas enviadas a un hilo que no ha sido respondido. Aquí está el reporte de mensaje sin responder en tu cuenta:

Unanswered sent emails and unanswered sent replies threadsUnanswered sent emails and unanswered sent replies threadsUnanswered sent emails and unanswered sent replies threads

SE también ofrece esta información como un resumen opcional, llamado el resumen de correo electrónico sin responder. Puedes recibirlo todos los días, cada ciertos días o cada semana.

También usamos tabulación SQL similar con Google Charts para proporcionar reportes de qué tan frecuentemente ciertas personas te envían correo electrónico:

Reports of inbound email frequency by senderReports of inbound email frequency by senderReports of inbound email frequency by sender
1
  public function reportInbound($account_id,$range=30,$limit = 100) {
2
    $result= Yii::app()->db->createCommand('SELECT fi_sender.personal, fi_sender.email,count(sender_id) as cnt

3
      FROM fi_message LEFT JOIN fi_sender ON fi_sender.id =fi_message.sender_id WHERE fi_sender.account_id = :account_id AND fi_message.created_at > DATE_SUB( NOW() , INTERVAL :range DAY )  

4
      GROUP BY sender_id ORDER BY cnt desc LIMIT :limit ')->bindValue('range',$range)->bindValue('account_id',$account_id)->bindValue('limit',$limit)->queryAll();
5
    return $result;
6
  }

Estaré escribiendo más sobre Google Charts para Tuts+ pronto.

Siguientes Pasos

Espero que hayas encontrado a Simplify Email lo suficientemente intrigante para probar en programación PHP IMAP. También muchas características geniales que puedes construir sin ninguna necesidad de grandes proveedores de correo electrónico para hacer cualquier cosa nueva.

Si tienes cualquier pregunta o correcciones, por favor publícalas en los comentarios. Si quisieras mantenerte al día con mis futuros turoriales Tuts+ y otras series, por favor sígueme @reifman o visita mi página de autor. También puedes contactarme aquí.

Enlaces Relacionados

Aquí hay algunos enlaces adicionales que podrías encontrar útiles:

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.