«Напомните через месяц?»: как автоматизировать напоминания клиентам с Golang, SQLite и вебхуками

7b167f7638fd8cb7cfb72909afaf1c6c.png

Привет, Хабр! Представим ситуацию: вы клиент. Разговор с менеджером завершен, он предложил вам что-то полезное — услугу, продукт или подписку — и, допустим, вы соглашаетесь: «Почему бы и нет, отличная идея». Менеджер записал ваше согласие и обещал напомнить вам через месяц. Звучит просто.

Но вот в реальности ни один менеджер не помнит про сотни обещаний клиентам. И здесь на помощь приходит автоматизация. В этой статье рассмотрим, как построить систему автоматического напоминания, которая избавит менеджеров от лишней работы и увеличит количество сделок, которые могли бы улетучиться.

Для реализации понадобятся:

  • Golang — основная платформа, на которой будем писать все сервисы.

  • Exolve API — для обработки звонков и получения текстовой расшифровки разговоров.

  • SQLite с GORM — база данных для хранения согласий клиентов, анализа и отчетности.

  • Webhook — для автоматического запуска цепочки при готовности транскрибации.

  • HTTP и JSON — для работы с API и передачи данных в запросах.

Активация транскрибации

Чтобы система могла анализировать согласие клиента, нужна текстовая расшифровка звонков.

Для начала активируем транскрибацию на номере, с которого идут звонки.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"
)

// Загружаем API-ключ из переменных окружения
var apiKey = os.Getenv("EXOLVE_API_KEY")

// enableTranscription включает транскрибацию на указанном номере
func enableTranscription(numberCode uint64) error {
    endpoint := "https://api.exolve.ru/number/v1/SetCallTranscribationState"
    payload := map[string]interface{}{
        "number_code":        numberCode,
        "call_transcribation": true,
    }
    body, err := json.Marshal(payload)
    if err != nil {
        return fmt.Errorf("ошибка сериализации: %w", err)
    }

    req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(body))
    if err != nil {
        return fmt.Errorf("ошибка создания запроса: %w", err)
    }
    req.Header.Set("Authorization", "Bearer "+apiKey)
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return fmt.Errorf("ошибка отправки запроса: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("не удалось включить транскрибацию, статус: %d", resp.StatusCode)
    }

    log.Println("Транскрибация включена успешно.")
    return nil
}

func main() {
    err := enableTranscription(79991112233)
    if err != nil {
        log.Fatalf("Ошибка активации транскрибации: %v", err)
    }
}

Функция enableTranscription отправляет запрос на активацию транскрибации для указанного номера. Передаем API-ключ в заголовке авторизации, чтобы подтвердить права. Если что-то пошло не так — код выведет понятное сообщение об ошибке. Основное здесь — отлавливать ошибки на каждом этапе, чтобы, если что-то пошло не так, система не просто «глохла», а давала обратную связь.

Получение текста разговора и анализ согласия

Теперь есть возможность получать текстовую расшифровку каждого звонка. Настало время разобраться, сказал ли клиент «да» или «нет». Для этого напишем функцию getTranscription, которая извлекает текст, и функцию analyzeConsent, проверяющую наличие согласия клиента.

func getTranscription(uid uint64) (string, error) {
    endpoint := "https://api.exolve.ru/statistics/call-record/v1/GetTranscribation"
    payload := map[string]interface{}{
        "uid": uid,
    }
    body, err := json.Marshal(payload)
    if err != nil {
        return "", fmt.Errorf("ошибка сериализации: %w", err)
    }

    req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(body))
    if err != nil {
        return "", fmt.Errorf("ошибка создания запроса: %w", err)
    }
    req.Header.Set("Authorization", "Bearer "+apiKey)
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return "", fmt.Errorf("ошибка получения транскрибации: %w", err)
    }
    defer resp.Body.Close()

    var result map[string]interface{}
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return "", fmt.Errorf("ошибка декодирования ответа: %w", err)
    }

    chunks := result["transcribation"].([]interface{})[0].(map[string]interface{})["chunks"].(map[string]interface{})
    text := chunks["text"].(string)
    return text, nil
}

func analyzeConsent(text string) bool {
    consentKeywords := []string{"да", "согласен", "конечно"}
    for _, keyword := range consentKeywords {
        if strings.Contains(strings.ToLower(text), keyword) {
            return true
        }
    }
    return false
}

getTranscription извлекает текстовую расшифровку по uid звонка. Ошибки обрабатываются на каждом этапе, так что если произойдет сбой — точно узнаем об этом.

analyzeConsent проверяет, есть ли в тексте слова, указывающие на согласие. Функция возвращает true, если одно из слов найдено.

Теперь есть текст разговора, и можно понять, хочет ли клиент напоминание. Дальше подключаем БД.

Сохраняем данные о согласии в базе данных

Если клиент согласился на напоминание, хочется сохранить это в базе данных для последующего анализа. Это позволит отслеживать конверсию, считать успешные напоминания и вести отчетность.

import (
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
    "log"
)

type Consent struct {
    ID       uint   `gorm:"primaryKey"`
    UID      uint64 `gorm:"unique;not null"`
    Text     string `gorm:"not null"`
    Agreed   bool   `gorm:"not null"`
}

func initDB() (*gorm.DB, error) {
    db, err := gorm.Open(sqlite.Open("consents.db"), &gorm.Config{})
    if err != nil {
        return nil, err
    }
    db.AutoMigrate(&Consent{})
    return db, nil
}

func saveConsent(db *gorm.DB, uid uint64, text string, agreed bool) error {
    consent := Consent{UID: uid, Text: text, Agreed: agreed}
    return db.Create(&consent).Error
}

Используем SQLite для простоты, но можно использовать любую базу данных. Функция saveConsent сохраняет UID звонка, текст и результат анализа (согласие или отказ) в базу. Это пригодится, если нужно строить аналитику по клиентам.

Отправка SMS с поддержкой повторных попыток

Допустим, клиент согласился на напоминание. Теперь задача — отправить ему SMS через месяц. Но, как известно, отправка SMS может быть не всегда надежной: сбои сети, проблемы на стороне оператора. Поэтому еще добавим механизм повторных попыток.

import "time"

func scheduleSMS(db *gorm.DB, uid uint64, to, message string) {
    delay := 30 * 24 * time.Hour
    time.AfterFunc(delay, func() {
        retryCount := 3
        for i := 0; i < retryCount; i++ {
            if sendSMS(to, message) {
                log.Printf("SMS отправлено для UID: %d", uid)
                break
            } else {
                log.Printf("Ошибка отправки SMS для UID: %d, попытка %d", uid, i+1)
                time.Sleep(5 * time.Second)
            }
        }
    })
}

func sendSMS(to, message

 string) bool {
    // Здесь будет реальная логика отправки SMS
    return true
}

Функция scheduleSMS планирует отправку SMS через месяц, а если попытка отправки не удалась, повторяет ее до трёх раз. Если все попытки провалены, система просто зафиксирует ошибку в логе.

Используем webhook для полной автоматизации

Чтобы сделать систему полностью автономной, добавим webhook. Он будет автоматом запускать анализ текста и отправку SMS, когда расшифровка разговора готова. Это избавляет от необходимости вручную запускать проверку для каждого звонка.

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func handleWebhook(db *gorm.DB) func(c *gin.Context) {
    return func(c *gin.Context) {
        var payload struct {
            UID uint64 `json:"uid"`
        }
        if err := c.BindJSON(&payload); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "неверный формат данных"})
            return
        }

        text, err := getTranscription(payload.UID)
        if err != nil {
            log.Printf("Ошибка получения транскрибации: %v", err)
            return
        }

        agreed := analyzeConsent(text)
        if err := saveConsent(db, payload.UID, text, agreed); err != nil {
            log.Printf("Ошибка сохранения согласия: %v", err)
        }

        if agreed {
            scheduleSMS(db, payload.UID, "+79991112233", "Напоминаем о нашем предложении!")
        }
    }
}

func main() {
    db, err := initDB()
    if err != nil {
        log.Fatalf("Ошибка инициализации базы данных: %v", err)
    }

    r := gin.Default()
    r.POST("/webhook", handleWebhook(db))
    r.Run(":8080")
}

Webhook позволяет Exolve автоматически уведомлять сервер, когда расшифровка готова.

Заключение

Вот и всё — создали простую автоматическую систему напоминаний, которая берет на себя процесс от звонка до SMS. Эту систему легко расширить и улучшить: подключить аналитику для отслеживания конверсий, добавить улучшенный NLP для анализа сложных ответов, а также интегрировать дополнительные каналы связи.

Если хотите углубиться в детали API и узнать больше о возможностях МТС Exolve, рекомендую ознакомиться с документацией Exolve.

© Habrahabr.ru