«Напомните через месяц?»: как автоматизировать напоминания клиентам с Golang, SQLite и вебхуками
- среда, 27 ноября 2024 г. в 00:00:09
Привет, Хабр! Представим ситуацию: вы клиент. Разговор с менеджером завершён, он предложил вам что-то полезное — услугу, продукт или подписку — и, допустим, вы соглашаетесь: «Почему бы и нет, отличная идея». Менеджер записал ваше согласие и обещал напомнить вам через месяц. Звучит просто.
Но вот в реальности ни один менеджер не помнит про сотни обещаний клиентам. И здесь на помощь приходит автоматизация. В этой статье рассмотрим, как построить систему автоматического напоминания, которая избавит менеджеров от лишней работы и увеличит количество сделок, которые могли бы улетучиться.
Для реализации понадобятся:
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 может быть не всегда надёжной: сбои сети, проблемы на стороне оператора. Поэтому добавим механизм повторных попыток.
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. Он будет автоматом запускать анализ текста и отправку 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.