איך לטפל במשתמשים אופליין בקמפיינים של RBM

גורם משמעותי בזמן המסירה של ההודעות של הנציג הוא אם למשתמש שאליו אתם מנסים להגיע יש חיבור נתונים בזמן שהנציג שולח הודעה. אם המשתמש לא מחובר לאינטרנט, פלטפורמת RBM מאחסנת את ההודעה ומנסים לשלוח אותה למשך עד 30 יום. אם לא ניתן להעביר את ההודעה עד אז, היא תוסר מהמערכת.

יש הרבה סיבות ומצבים שבהם למשתמש אין קישוריות כשהנציג מנסה ליצור איתו קשר. יכול להיות שהם השביתו את חבילת הגלישה כדי לחסוך כסף בחבילת הגלישה, יכול להיות שהם היו במטוס בלי חיבור Wi-Fi, או שהם נוסעים ברכבת התחתית בתוך מנהרה. בהתאם לדחיפות שבה ההודעות שלכם צריכות להגיע, הנציג צריך לטפל בחינניות במשתמשים אופליין על ידי ביטול הודעות RBM שלא נמסרו וניתוב מחדש של ההודעות האלה לערוץ אחר.

במאמר הזה נסביר איך להשתמש ב-Google Cloud Datastore כדי לעקוב אחרי ההודעות שאתם שולחים ומעבירים, איך להשתמש ב-cron כדי revoke הודעות שלא נמסרו ואיך לנתב מחדש את ההודעות האלה באמצעות SMS.

מעקב אחר הודעות שנשלחו

כל הודעה ששולחת הנציג ב-RBM צריכה לכלול מזהה הודעה ייחודי. כדי לעקוב אחרי ההודעות שהנציג שולח, צריך לשמור את מזהה ההודעה, מספר הטלפון וחותמת הזמן של כל הודעה.

אתם יכולים להשתמש במגוון טכנולוגיות כדי לאחסן את המידע הזה, אבל במאמר הזה נשתמש ב-Google Cloud Datastore. Cloud Datastore הוא מסדי נתונים מסוג NoSQL שניתן להתאמה לעומס, שמטפל באופן אוטומטי בפיצול וביצירת רפליקות. זהו פתרון מצוין לאחסון נתונים שאינם יחסיים, כמו הודעות שנשלחות על ידי נציג. כדי להשתמש ב-Cloud Datastore יש צורך במכונה פעילה של Google App Engine, אני משתמשת ב-App Engine כדי לארח את הסוכן RBM ולהגדיר משימת cron.

ספריות לקוח של Cloud Datastore זמינות בשפות רבות. לצורך הדוגמה הזו, אני משתמש ב-Node.js ומבסס את הקוד של סוכן ה-RBM על דוגמת ה-First Agent Node.js שזמינה באתר המפתחים של RBM. הדבר הראשון שצריך לעשות הוא להתקין את Cloud Datastore לפרויקט Node.js באמצעות הרצת הפקודה הבאה:

npm install --save @google-cloud/datastore

בקוד המקור של הנציג, הוספתי הפניה גלובלית לספריית הלקוח של Cloud Datastore.

// Imports the Google Cloud client library
const Datastore = require('@google-cloud/datastore');

// Creates a client
const datastore = new Datastore({
    projectId: PROJECT_ID,
});

אחרי שיוצרים אובייקט של מאגר הנתונים, מוסיפים פונקציה לשמירת המצב msisdn, message id, sent time ו-delivery לכל הודעה.

/**
 *   Records an entry in the Cloud Datastore to keep track of the
 *   messageIds sent to users and the delivery state.
 *
 *   @property {string} msisdn The user's phone number in E.164 format.
 *   @property {string} messageId The unique message identifier.
 *   @property {boolean} delivered True if message has been delivered.
 */
function saveMessage(msisdn, messageId, delivered) {
    const messageKey = datastore.key(['Message', messageId]);

    const dataForMessage = {
        key: messageKey,
        data: {
            id: messageId,
            msisdn: msisdn,
            lastUpdated: new Date().getTime(),
            delivered: delivered
        },
    };

    // Record that the message was sent.
    datastore
        .save(dataForMessage)
        .then(function() {
            console.log('saved message successfully');
        })
        .catch((err) => {
            console.error('ERROR:', err);
        });
}

כשהפונקציה הזו פועלת, צריך להפעיל את השיטה הזו בכל פעם שהנציג שולח הודעה למשתמש. כשספריית הלקוח של RBM Node.js שולחת הודעות ב-RBM, הספרייה מספקת אובייקט תגובה ב-method של הקריאה החוזרת (callback) שמכיל את ה-messageId של ההודעה שנשלחה למשתמש.

לפניכם דוגמה לשליחת הודעת טקסט פשוטה למשתמש ולהקלטת פרטי ההודעות אחרי תקשורת מוצלחת עם RBM API.

let params = {
    messageText: 'Hello, World!',
    msisdn:'+12223334444',
};

// Send "Hello, World!" to the user.
rbmApiHelper.sendMessage(params,
function(response) {
    // Extract the message id from the response
    let messageId = response.config.params.messageId;

    // Store the sent state in the Datastore
    saveMessage(phoneNumber, messageId, false);
});

אחרי שמריצים את הקוד שלמעלה, אפשר לבדוק את מאגר הנתונים ממסוף Google Cloud בתצוגה ישויות של מאגר הנתונים.

Datastore

עדכון מצב המסירה של הודעות

עכשיו, אחרי שהנציג מאחסן בקשות להודעות שנשלחו ב-Datastore, אנחנו צריכים לעדכן את סטטוס המסירה ברגע שההודעה תימסר למכשיר של המשתמש.

המכשירים של המשתמשים שולחים אירועים של DELIVERED, READ ו-IS_TYPING לנציגי RBM באמצעות Cloud Pub/Sub. ב-handler של Pub/Sub, בודקים אם יש אירועים שנמסרו ומעדכנים את ההגדרה ב-Datastore לגבי הסימון שנמסר כ-true.

/**
 *   Uses the event received by the Pub/Sub subscription to send a
 *   response to the client's device.
 *   @param {object} userEvent The JSON object of a message
 *   received by the subscription.
 */
function handleMessage(userEvent) {
    if (userEvent.senderPhoneNumber != undefined) {
        let msisdn = userEvent.senderPhoneNumber;
        let messageId = userEvent.messageId;
        let eventType = userEvent.eventType;

        if(eventType === 'DELIVERED') {
            saveMessage(msisdn, messageId, true);
        }

        // TODO: Process message and create RBM response
    }
}

הנציג שומר את ההודעות היוצאות ב-Datastore, ומעדכן את הנתונים כשהוא מקבל התראה על מסירה. בקטע הבא אנחנו מגדירים משימת cron ב-App Engine של Google שתפעל בכל 10 דקות כדי לעקוב אחר הודעות שלא נמסרו.

cron ב-App Engine של Google

אתם יכולים להשתמש במשימות cron כדי לתזמן משימות במרווחים שונים. ב-App Engine של Google, משימת cron מוגדרת עם תיאור, כתובת URL ומרווח.

באפליקציות Node.js צריך להגדיר אותן בקובץ cron.yaml, שאותו אפשר לפרוס ב-App Engine באמצעות Google Cloud SDK. תוכלו לקרוא מידע על הגדרות אחרות של הגדרות שפה במאמר Schedule Tasks with cron.yaml.

מכיוון שלמשימת cron נדרשת כתובת URL, עליכם להוסיף נקודת קצה (endpoint) של כתובת URL לנתב של האפליקציה האקספרס כדי לקרוא לו cron. ה-webhook הזה אחראי לשליחת שאילתות ל-Datastore לגבי הודעות ישנות, למחיקתן מפלטפורמת RBM ולשליחתן למשתמש ב-SMS.

router.get('/expireMessages', function(req, res, next) {
    // TOOD: Query the Datastore for undelivered messages,
    // remove them from the RBM platform, and send them over SMS

    res.status(200).send();
});

בהמשך מופיעות ההגדרות של הקובץ cron.yaml שמריצים את נקודת הקצה בכל 10 דקות.

cron:
-   description: "Processing expired RBM messages"
  url: /expireMessages
  schedule: every 10 mins

כדי לפרוס את משימות ה-cron ב-App Engine, מריצים את הפקודה הבאה:

gcloud app deploy cron.yaml

אחרי הפריסה, משימת ה-cron מוגדרת אוטומטית ב-App Engine, ותוכלו לראות את המשימה בקטע App Engine > משימות cron.

משימות cron

שליחת שאילתה ל-Datastore לגבי הודעות שלא נמסרו

ב-webhook של משימת cron שהגדרתם בקטע הקודם, צריך להוסיף לוגיקה כדי לחפש את כל ההודעות שנשלחות שהמצב delivered שלהן שווה ל-False, ושהזמן שלהן לפני lastUpdated גדול יותר מהזמן הקצוב לתפוגה שהוגדר מראש, שמתאים לתרחיש לדוגמה שלנו. בדוגמה הזו, התוקף של הודעות מלפני יותר משעה.

כדי לתמוך בשאילתה מורכבת כמו זו, ל-Datastore צריך להיות אינדקס מורכב שמכיל גם את הנכס delivered וגם את הנכס lastUpdated. כדי לעשות זאת, אפשר ליצור בפרויקט קובץ בשם index.yaml שמצוינים בו:

indexes:
-   kind: Message
  properties:
  -   name: delivered
    direction: asc
  -   name: lastUpdated
    direction: desc

בדומה לפריסה של משימת cron שהגדרתם בעבר, השתמשו ב-Google Cloud SDK כדי לפרוס את האינדקס המורכב שהגדרתם באמצעות הפקודה הבאה:

gcloud datastore create-indexes index.yaml

אחרי הפריסה, ה-App Engine מגדיר את האינדקס באופן אוטומטי, ואפשר להציג את האינדקס בקטע Datastore > Indexes.

מדדים של Datastore

אחרי שתגדירו את האינדקס, נוכל לחזור ל-webhook שיצרתם עבור משימת cron ולסיים את הלוגיקה של תפוגת התוקף של ההודעות:

router.get('/expireMessages', function(req, res, next) {
    // Milliseconds in an hour
    const TIMEOUT = 3600000;

    // Threshold is current time minus one hour
    const OLD_MESSAGE_THRESHOLD = new Date().getTime() - TIMEOUT;

    // Create a query to find old undelivered messages
    const query = datastore
        .createQuery('Message')
        .filter('delivered', '=', false)
        .filter('lastUpdated', '<', OLD_MESSAGE_THRESHOLD);

    // Execute the query
    datastore.runQuery(query).then((results) => {
        for(var i = 0; i < results[0].length; i++) {
            let msisdn = results[0][i].msisdn;
            let messageId = results[0][i].id;

            // Stop the message from being sent
            rbmApiHelper.revokeMessage(msisdn, messageId);

            // Remove the message from the Datastore
            datastore.delete(results[0][i][datastore.KEY]);

            // TODO: Send the user the message as SMS
        }
    });

    res.status(200).send();
});

RBM לא תומך במקור ב-SMS חלופי, לכן תצטרכו להטמיע את הלוגיקה לשליחת הודעות שלא נמסרו ב-SMS.

סיכום ו-TL;DR

כדי לטפל במשתמשים אופליין, אפשר ליצור לוגיקת ביטול להודעות RBM שלא נמסרו. משך הזמן שבו צריך להשתמש לפני תפוגת ההודעה תלוי במידת הזמן של המידע שאתם משדרים. במקרה של מידע תלוי-זמן, מומלץ זמן קצוב לתפוגה של פחות משעתיים.

בדוגמה הזו נעשה שימוש ב-Cloud Datastore וב-Google App Engine כדי לנהל את תהליך האחסון, השאילתות והביטול, אבל כל מנגנון אחסון למעקב אחרי ההודעות שנשלחו ונשלחות אמור לפעול.

בהצלחה ושיהיה בהצלחה!