1. Code
  2. PHP
  3. Yii

Creando su startup: Enviando recordatorios

Este tutorial forma parte de la serie Creando su Startup con PHP en Envato Tuts +. En esta serie, te estoy guiando a través del lanzamiento de un inicio de concepto a realidad utilizando mi aplicación de Meeting Planner como un ejemplo de la vida real. Cada paso a lo largo del camino, voy a lanzar el código de Meeting Planner como ejemplo en código abierto del que puede aprender. También me ocuparé de asuntos relacionados de negocios con el startup a medida que surjan.
Scroll to top
This post is part of a series called Building Your Startup With PHP.
Building Your Startup: Dynamic Ajax Forms for Scheduling
Building Your Startup: The Dashboard Foundation

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

Este tutorial forma parte de la serie Creando su Startup con PHP en Envato Tuts +. En esta serie, te estoy guiando a través del lanzamiento de un inicio de concepto a realidad utilizando mi aplicación de Meeting Planner como un ejemplo de la vida real. Cada paso a lo largo del camino, voy a lanzar el código de Meeting Planner como ejemplo en código abierto del que puede aprender. También me ocuparé de asuntos relacionados de negocios con el startup a medida que surjan.

En este tutorial en dos partes, describo cómo construí la infraestructura para los recordatorios y su entrega. Hoy, voy a guiarlo a través de monitoreo cuando entregar recordatorios y cómo enviar los correos electrónicos.

Si aún no ha probado Meeting Planner, siga adelante y programe su primera reunión. Participo en los hilos del comentario abajo, así que ¡dígame lo que usted piensa! Me interesa especialmente si desea sugerir nuevas funciones o temas para futuros tutoriales.

Como recordatorio, todo el código para Meeting Planner está escrito en el Framework Yii2 para PHP. Si desea obtener más información acerca de Yii2, consulte nuestra serie paralela Programación con Yii2.

Tiempo de supervisión para recordatorios

A medida que pasa el tiempo, debemos vigilar la tabla MeetingReminder para saber cuándo entregar recordatorios. Idealmente, queremos que los recordatorios sean entregados exactamente a tiempo, ej. Al minuto.

Ejecución de tareas de fondo

La puntualidad depende de la periodicidad con que ejecutamos las tareas de fondo para el monitoreo. Actualmente, en nuestra etapa pre-alpha, los estoy ejecutando cada cinco minutos:

1
# m h  dom mon dow   command

2
*/5 * * * * wget -O /dev/null http://meetingplanner.io/daemon/frequent

Este script llama a MeetingReminder::check(), que encuentra los recordatorios de reunión que se deben y las solicitudes a process():

1
// frequent cron task will call to check on due reminders

2
public static function check() {
3
  $mrs = MeetingReminder::find()->where('due_at<='.time().' and status='.MeetingReminder::STATUS_PENDING)->all();
4
  foreach ($mrs as $mr) {
5
    // process each meeting reminder

6
    MeetingReminder::process($mr);
7
  }
8
}

Procesamiento de un recordatorio

MeetingReminder::process() reúne los detalles necesarios para crear un correo electrónico de recordatorio. Esto incluye el destinatario del recordatorio, los detalles de la reunión y la hora:

1
public static function process($mr) {
2
      // fetch the reminder

3
      // deliver the email or sms

4
      // send updates about recent meeting changes made by $user_id

5
      $user_id = $mr->user_id;
6
      $meeting_id = $mr->meeting_id;
7
      $mtg = Meeting::findOne($meeting_id);
8
      // only send reminders for meetings that are confirmed

9
      if ($mtg->status!=Meeting::STATUS_CONFIRMED) return false;
10
      // only send reminders that are less than a day late - to do - remove after testing period

11
      if ((time()-$mr->due_at)>(24*3600+1)) return false;
12
      $u = \common\models\User::findOne($user_id);
13
      // ensure there is an auth key for the recipient user

14
      if (empty($u->auth_key)) {
15
        return false;
16
      }
17
      // prepare data for the message

18
      // get time

19
      $chosen_time = Meeting::getChosenTime($meeting_id);
20
      $timezone = MiscHelpers::fetchUserTimezone($user_id);
21
      $display_time = Meeting::friendlyDateFromTimestamp($chosen_time->start,$timezone);
22
      // get place

23
      $chosen_place = Meeting::getChosenPlace($meeting_id);
24
25
      $a=['user_id'=>$user_id,
26
       'auth_key'=>$u->auth_key,
27
       'email'=>$u->email,
28
       'username'=>$u->username
29
     ];
30
       // check if email is okay and okay from this sender_id

31
      if (User::checkEmailDelivery($user_id,0)) {
32
          // Build the absolute links to the meeting and commands

33
          $links=[
34
            'home'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_HOME,0,$a['user_id'],$a['auth_key']),
35
            'view'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_VIEW,0,$a['user_id'],$a['auth_key']),
36
            'footer_email'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_EMAIL,0,$a['user_id'],$a['auth_key']),
37
            'footer_block'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK,0,$a['user_id'],$a['auth_key']),
38
            'footer_block_all'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK_ALL,0,$a['user_id'],$a['auth_key']),
39
            'running_late'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_RUNNING_LATE,0,$a['user_id'],$a['auth_key']),
40
            'view_map'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_VIEW_MAP,0,$a['user_id'],$a['auth_key'])
41
          ];
42
          // send the message

43
          $message = Yii::$app->mailer->compose([
44
            'html' => 'reminder-html',
45
            'text' => 'reminder-text'
46
          ],
47
          [
48
            'meeting_id' => $mtg->id,
49
            'sender_id'=> $user_id,
50
            'user_id' => $a['user_id'],
51
            'auth_key' => $a['auth_key'],
52
            'display_time' => $display_time,
53
            'chosen_place' => $chosen_place,
54
            'links' => $links,
55
            'meetingSettings' => $mtg->meetingSettings,
56
        ]);
57
          if (!empty($a['email'])) {
58
            $message->setFrom(['support@meetingplanner.com'=>'Meeting Planner']);
59
            $message->setTo($a['email'])
60
                ->setSubject(Yii::t('frontend','Meeting Reminder: ').$mtg->subject)
61
                ->send();
62
          }
63
       }
64
      $mr->status=MeetingReminder::STATUS_COMPLETE;
65
      $mr->update();
66
    }

La función User::checkEmailDelivery() verifica que el usuario no ha bloqueado los correos electrónicos del sistema (o de personas en particular). Se asegura de que está bien enviar el recordatorio:

1
// check if email is okay and okay from this sender_id

2
      if (User::checkEmailDelivery($user_id,0)) {
3
          // Build the absolute links to the meeting and commands

4
          $links=[
5
            'home'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_HOME,0,$a['user_id'],$a['auth_key']),
6
            'view'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_VIEW,0,$a['user_id'],$a['auth_key']),
7
            'footer_email'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_EMAIL,0,$a['user_id'],$a['auth_key']),
8
            'footer_block'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK,0,$a['user_id'],$a['auth_key']),
9
            'footer_block_all'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK_ALL,0,$a['user_id'],$a['auth_key']),
10
            'running_late'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_RUNNING_LATE,0,$a['user_id'],$a['auth_key']),
11
            'view_map'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_VIEW_MAP,0,$a['user_id'],$a['auth_key'])
12
          ];
13
      

Aquí tiene el método User::checkEmailDelivery. En primer lugar, comprueba si el usuario ha bloqueado completamente el correo electrónico (o no) o si el mensaje se envía desde un usuario bloqueado:

1
    public static function checkEmailDelivery($user_id,$sender_id) {
2
      // check if this user_id receives email and if sender_id not blocked

3
      // check if all email is turned off

4
      $us = UserSetting::safeGet($user_id);
5
      if ($us->no_email != UserSetting::EMAIL_OK) {
6
        return false;
7
      }
8
      // check if no sender i.e. system notification

9
      if ($sender_id==0) {
10
        return true;
11
      }
12
      // check if sender is blocked

13
      $ub = UserBlock::find()->where(['user_id'=>$user_id,'blocked_user_id'=>$sender_id])->one();
14
      if (!is_null($ub)) {
15
        return false;
16
      }
17
      return true;
18
    }

La nueva plantilla de correo electrónico de recordatorio

En el episodio Entregando tu invitación de la reunión, escribí sobre el envío de mensajes de correo electrónico dentro del marco Yii. En refinando plantillas de correo electronico, describí la actualización de las plantillas para nuestras nuevas plantillas de respuesta basadas en oxígeno.

Aquí está la nueva plantilla de correo electrónico reminder_html.php:

1
<?php
2
use yii\helpers\Html;
3
use yii\helpers\Url;
4
use common\components\MiscHelpers;
5
use frontend\models\Meeting;
6
use frontend\models\UserContact;
7
/* @var $this \yii\web\View view component instance */
8
/* @var $message \yii\mail\BaseMessage instance of newly created mail message */
9
?>
10
<tr>
11
  <td align="center" valign="top" width="100%" style="background-color: #f7f7f7;" class="content-padding">
12
    <center>
13
      <table cellspacing="0" cellpadding="0" width="600" class="w320">
14
        <tr>
15
          <td class="header-lg">
16
            Reminder of Your Meeting
17
          </td>
18
        </tr>
19
        <tr>
20
          <td class="free-text">
21
            Just a reminder about your upcoming meeting <?php echo $display_time; ?>
22
            <?php
23
            // this code is similar to code in finalize-html

24
            if ($chosen_place!==false) {
25
            ?>
26
            &nbsp;at <?php echo $chosen_place->place->name; ?>&nbsp;
27
              (<?php echo $chosen_place->place->vicinity; ?>, <?php echo HTML::a(Yii::t('frontend','map'),$links['view_map']); ?>)
28
              <?php
29
            } else {
30
            ?>
31
            &nbsp;via phone or video conference.
32
            <?php
33
              }
34
            ?>
35
            <br />
36
            Click below to view more details to view the meeting page.
37
          </td>
38
        </tr>
39
      <tr>
40
        <td class="button">
41
          <div><!--[if mso]>

42
            <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="http://" style="height:45px;v-text-anchor:middle;width:155px;" arcsize="15%" strokecolor="#ffffff" fillcolor="#ff6f6f">

43
              <w:anchorlock/>

44
              <center style="color:#ffffff;font-family:Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;">My Account</center>

45
            </v:roundrect>

46
          <![endif]--><a class="button-mobile" href="<?php echo $links['view'] ?>"
47
          style="background-color:#ff6f6f;border-radius:5px;color:#ffffff;display:inline-block;font-family:'Cabin', Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;line-height:45px;text-align:center;text-decoration:none;width:155px;-webkit-text-size-adjust:none;mso-hide:all;">Visit Meeting Page</a></div>
48
        </td>
49
      </tr>
50
      <tr>
51
        <td class="mini-large-block-container">
52
          <table cellspacing="0" cellpadding="0" width="100%"  style="border-collapse:separate !important;">
53
            <tr>
54
              <td class="mini-large-block">
55
                <table cellpadding="0" cellspacing="0" width="100%">
56
                  <tr>
57
                    <td style="text-align:left; padding-bottom: 30px;">
58
                      <strong>Helpful options:</strong>
59
                      <p>
60
                        <?php
61
                          echo HTML::a(Yii::t('frontend','Inform them I\'m running late.'),$links['running_late']);
62
                        ?>
63
                      </p>
64
                    </td>
65
                  </tr>
66
                </table>
67
              </td>
68
            </tr>
69
          </table>
70
        </td>
71
      </tr>
72
    </table>
73
  </center>
74
  </td>
75
</tr>
76
<?php echo \Yii::$app->view->renderFile('@common/mail/section-footer-dynamic.php',['links'=>$links]) ?>

Incluye la fecha, la hora y la ubicación elegida (con una dirección y un enlace de mapa). También he agregado los principios de un área de opciones útil con un comando inicial, "Informales que estoy llegando tarde":

Meeting Planner - Reminder of Your Meeting EmailMeeting Planner - Reminder of Your Meeting EmailMeeting Planner - Reminder of Your Meeting Email

Al hacer clic en él, enviaremos por correo electrónico o SMS a los otros participantes que podrían tardar entre cinco y diez minutos. No hay nada más que hacer o escribir mientras tiene prisa.

Tal vez una eventual versión móvil de Meeting Planner conocerá su ubicación GPS y hacerles saber aproximadamente cuán lejos está usted. He empezado a seguir ideas como esta en Asana para la planificación de productos—preguntaré a las diosas editoriales de Envato Tuts+ (mostradas a continuación) si puedo escribir sobre la implementación de seguimiento de características y emisiones en un futuro tutorial.

Meeting Planner Reminders - Game of Thrones Daenerys Targaryen and Dragon our editorial goddessMeeting Planner Reminders - Game of Thrones Daenerys Targaryen and Dragon our editorial goddessMeeting Planner Reminders - Game of Thrones Daenerys Targaryen and Dragon our editorial goddess

Mejoras en los recordatorios

El correo electrónico de recordatorio puede utilizar algunas funciones mejoradas:

  • Completar la implementación de correo electrónico que se ejecuta tarde.
  • Visualización de la información de contacto de otros participantes, como números de teléfono y direcciones de correo electrónico. El correo electrónico que se ejecuta tarde puede mostrar sólo la información de contacto de la persona que llega tarde.
  • Muestra un mapa de Google estático que muestra la ubicación de la reunión.
  • Enlace a una función para solicitar o requerir una reprogramación de la reunión.
  • Enlace a no sólo el mapa, sino las direcciones de la ubicación.
  • Enlace para ajustar los recordatorios.

Resulta que la mayoría de estas características requieren más trabajo del que hay espacio para este tutorial.

Por ejemplo, la idea de enviar un correo electrónico de ejecución tarde parece una característica simple, ¿verdad? Es un buen ejemplo del reto que los frameworks MVC a veces imponen a los desarrolladores. La implementación de una función de correo electrónico de última ejecución requería código a través de varios archivos, incluida una nueva plantilla de correo electrónico.

Implementación de la función de ejecución tardía

Meeting Planner Reminders - Late Notice EmailMeeting Planner Reminders - Late Notice EmailMeeting Planner Reminders - Late Notice Email

En lugar de compartir todos los cambios de código necesarios para esta función, resumiré los lugares en los que era necesario cambiar el entorno:

  • El correo electrónico de recordatorio necesitaba un enlace con un nuevo comando
  • El COMMAND_RUNNING_LATE tenía que ser definido en el modelo y controlador de la reunión, y tenía que mostrar un mensaje de confirmación.

He aquí un ejemplo de lo que ves después de pedir que se envíe un aviso tardío:

Meeting Planner Reminders - Late Notice ConfirmationMeeting Planner Reminders - Late Notice ConfirmationMeeting Planner Reminders - Late Notice Confirmation
  • El método sendLateNotice() tuvo que ser construido en Meeting.php
  • La plantilla de correo electrónico de late-html.php tenían que ser construida. Esto incluye una opción para que el otro participante anuncie que están "tarde también".
  • El método UserContact::buildContactString() tuvo que completarse para incluir información de contacto para la persona que se está ejecutando tarde.
  • El ACTION_SENT_RUNNING_LATE tuvo que ser creado para registrar el envío de un aviso tardío en nombre de esta persona en el MeetingLog.
  • El método sendLateNotice() tuvo que comprobar el registro y mostrar un error si el aviso tardío ya se había enviado una vez.

Esto es lo que muestra el aviso tardío enviado:

Meeting Planner Reminders - Error message - Late Notice Already SentMeeting Planner Reminders - Error message - Late Notice Already SentMeeting Planner Reminders - Error message - Late Notice Already Sent

Era un montón de código para implementar lo que parecía una simple adición.

Esperé a probar la función hasta que todos los cambios anteriores se habían hecho, y me sorprendió gratamente que todos trabajaron exactamente como se pretende. Sólo tuve que hacer algunos cambios cosméticos en el texto.

Implementación de la información de contacto del participante

Si bien esta función ya existía para los archivos iCal, necesitaba completar esta función para las invitaciones de reunión basadas en correo electrónico. Así que extendí UserContact::buildContactString($user_id,$mode) para $mode = 'html'.

Aquí está el código actualizado:

1
public static function buildContactString($user_id,$mode='ical') {
2
    // to do - create a view for this that can be rendered

3
    $contacts = UserContact::getUserContactList($user_id);
4
    if (count($contacts)==0) return '';
5
    if ($mode=='ical') {
6
        $str='';
7
    } else if ($mode =='html') {
8
        $str='<p>';
9
    }
10
    $str = \common\components\MiscHelpers::getDisplayName($user_id).': ';
11
    if ($mode=='ical') {
12
        $str.=' \\n';
13
    } else if ($mode =='html') {
14
        $str.='<br />';
15
    }
16
    foreach ($contacts as $c) {
17
      if ($mode=='ical') {
18
        $str.=$c->friendly_type.': '.$c->info.' ('.$c->details.')\\n';
19
      } else if ($mode =='html') {
20
        $str.=$c->friendly_type.': '.$c->info.'<br />'.$c->details.'<br />';
21
      }
22
    }
23
    if ($mode=='ical') {
24
        $str.=' \\n';
25
    } else if ($mode =='html') {
26
        $str.='</p>';
27
    }
28
    return $str;
29
  }
30
}

Estoy seguro de que necesitará un poco de pulido a medida que avanzamos en las pruebas alfa y beta, pero la funcionalidad ya está ahí.

Puede ver los detalles de contacto mostrados en el aviso completo anterior, pero aquí está el segmento que genera:

Meeting Planner Reminder - Contact DetailsMeeting Planner Reminder - Contact DetailsMeeting Planner Reminder - Contact Details

Pulido de Recordatorios

Las cosas fueron tan bien en general con estas últimas mini-características que he añadido el enlace para ajustar sus recordatorios para el correo electrónico de recordatorio original también.

Con todo este nuevo código, estoy seguro de que estaré puliendo la función de recordatorios y mejorándola regularmente durante las próximas semanas. Sin embargo, como planificador de reuniones se ha unido, más funcionalidad es a menudo posible—a menudo con poco trabajo porque hay un framework y una base. El modelo de datos limpios y el framework MVC hacen regularmente mejoras incrementales relativamente sencillas.

Todo esto es lo que hace que la construcción de un startup tanto divertido y desafiante. Y trabajando con dragones (algunos días no puedo creer que me pagan para hacer esto).

¿Que sigue?

Meeting Planner ha hecho un tremendo progreso en los últimos meses. Estoy empezando a experimentar con WeFunder basado en la implementación de las nuevas reglas de crowdfunding de la SEC. Por favor considere seguir nuestro perfil. Espero escribir sobre esto más en un futuro tutorial.

Y, sin duda, la aplicación todavía tiene un montón de deficiencias—por favor asegúrese de publicar sus comentarios en los comentarios o abrir un ticket de soporte.

Espero que hayas disfrutado este episodio. Vea los próximos tutoriales en nuestra serie Creando su Startup Con PHP, todavía hay trabajo polaco por delante, pero también funciones más grandes.

Por favor, siéntase libre de añadir sus preguntas y comentarios a continuación; Generalmente participo en las discusiones. También puede contactarme directamente en Twitter @reifman.

Enlaces relacionados