Перейти к содержимому

Напоминания и рассылки без блокировок

Обновлено 17 октября 2026

Что получится: скрипт на Python, который рассылает напоминания о записи. Канал он выбирает по номеру клиента: WhatsApp, если номер там есть, иначе MAX. Пишет только в рабочие часы, держит паузы и соблюдает правила WhatsApp для первых сообщений. Если что-то отложено, скрипт сам повторит отправку позже. Повторный запуск не отправит второе напоминание.

список записей ──► канал по номеру ──► рабочие часы? ──► sendMessage
                   (checkAccount,       нет → позже       │
                    кэш у себя)                           ├─ 200 → отправлено
                                                          ├─ 429 → пауза или на завтра
                                                          └─ 400 → в отчёт, без повтора

Почему это важно

Мессенджер оценивает номер по поведению. Залпы одинаковых сообщений людям, которые давно не писали, похожи на спам — номер ограничат или заблокируют. Gratio держит базовые правила сам и отвечает 429, когда их нарушают, — приложению остаётся не спорить с ними:

Правило WhatsApp code при нарушении
Первое сообщение (новый чат или молчание 30 дней) — не чаще раза в минуту, не больше 200 в сутки; новому номеру — 20 в сутки 10 дней first_message_gap, first_message_limit
Одинаковый текст первого сообщения — не больше 10 в час same_text_limit
Без ответа собеседника — до 3 сообщений за 2 минуты и одно напоминание через сутки no_reply_limit, follow_up_too_early
WhatsApp ограничил номеру новые чаты restricted

Подробно — WhatsApp и Темп и лимиты.

Что понадобится

  • два канала с ключами: WhatsApp и MAX (можно один — тогда без выбора);
  • Python 3.10+, pip install requests;
  • файл bookings.csv с записями:
id,phone,name,course,time
1532,79991234567,Мария,Робототехника,2026-10-18 11:00
1533,79997654321,Олег,Python,2026-10-18 12:00

Шаг 1. Каналы и вызов API

Создайте файл remind.py. Ключи каналов — в переменной GRATIO_CHANNELS, по порядку предпочтения:

import csv
import json
import os
import random
import time
from datetime import datetime, timedelta
import requests

API = os.environ.get("GRATIO_URL", "https://api.gratio.net")
# по порядку предпочтения: {"whatsapp": ["7100000001", "токен"], "max": ["7100000002", "токен"]}
CHANNELS: dict[str, list[str]] = json.loads(os.environ["GRATIO_CHANNELS"])
WORK_HOURS = range(10, 20)                     # пишем с 10:00 до 19:59 по времени клиента


def call(channel: str, method: str, payload: dict) -> requests.Response:
    instance, token = CHANNELS[channel]
    return requests.post(f"{API}/waInstance{instance}/{method}/{token}", json=payload, timeout=90)

Шаг 2. Канал по номеру

checkAccount ищет аккаунт по номеру. Ответ сохраняем в файл: мессенджеры строго ограничивают поиск по номерам, и проверять одну базу снова и снова нельзя. Допишите в remind.py:

CACHE_FILE = "accounts.json"
cache: dict = json.load(open(CACHE_FILE)) if os.path.exists(CACHE_FILE) else {}


def pick_channel(phone: str) -> tuple[str, str] | None:
    for channel in CHANNELS:
        key = f"{channel}:{phone}"
        if key not in cache:
            r = call(channel, "checkAccount", {"phoneNumber": int(phone)})
            if r.status_code == 469:           # предел поиска — следующий канал
                continue
            r.raise_for_status()
            cache[key] = r.json()["chatId"] if r.json()["exist"] else ""
            json.dump(cache, open(CACHE_FILE, "w"))
        if cache[key]:
            return channel, cache[key]
    return None

Шаг 3. Отправка с разбором отказов

clientMessageId — из id записи: повторный запуск скрипта не отправит второе напоминание. Текст личный — с именем и временем, поэтому правило одинаковых текстов не срабатывает. Допишите:

LATER = {"first_message_limit", "restricted", "follow_up_too_early", "no_reply_limit"}


def send_reminder(row: dict) -> str:
    target = pick_channel(row["phone"])
    if target is None:
        return "нет мессенджера"
    channel, chat_id = target
    text = (f"{row['name']}, напоминаем: завтра в {row['time'][11:]} занятие «{row['course']}». "
            "Если планы изменились, ответьте на это сообщение — перенесём.")
    for _ in range(3):
        r = call(channel, "sendMessage", {"chatId": chat_id, "message": text,
                                          "clientMessageId": f"reminder-{row['id']}"})
        if r.status_code == 200:
            return f"отправлено в {channel}: {r.json()['idMessage']}"
        body = r.json()
        if r.status_code == 429 and body.get("code") not in LATER:
            time.sleep(int(r.headers.get("Retry-After", 60)))   # пауза темпа — ждём и повторяем
            continue
        if r.status_code == 429:
            return f"позже: {body['code']}"                      # дневной предел — завтра
        return f"ошибка {r.status_code} {body.get('code')}: {body.get('message')}"
    return "позже: темп"

Шаг 4. Цикл по записям

Допишите — файл готов:

def main() -> None:
    if datetime.now().hour not in WORK_HOURS:
        print("Вне рабочих часов — запустите позже")
        return
    tomorrow = (datetime.now() + timedelta(days=1)).date().isoformat()
    with open("bookings.csv", encoding="utf-8") as f:
        rows = [r for r in csv.DictReader(f) if r["time"].startswith(tomorrow)]
    for row in rows:
        print(row["id"], row["name"], "→", send_reminder(row))
        time.sleep(random.uniform(3, 6))       # свой темп между разными людьми


if __name__ == "__main__":
    main()

Шаг 5. Запустите и проверьте

export GRATIO_CHANNELS='{"whatsapp": ["7100000001", "токен"], "max": ["7100000002", "токен"]}'
python remind.py

Вы увидите:

1532 Мария → отправлено в whatsapp: 3EB0C767D26A1D5A6F0B
1533 Олег → отправлено в max: 117428956522361912

Запустите ещё раз — Gratio вернёт те же idMessage, второго сообщения не будет.

Запускайте скрипт по расписанию, например каждый час с 10 до 19: строки «позже» отправятся при следующем запуске.

Как снизить риск блокировки

  • Пишите тем, кто ждёт сообщения: клиентам с записью, а не по купленной базе.
  • Давайте отказаться: «Ответьте СТОП, если не хотите напоминаний» — и храните отказы у себя.
  • Отвечающие клиенты — лучший сигнал для мессенджера. Ответы приходят уведомлениями — см. Текст.
  • Новый номер разгоняйте постепенно: первые 10 дней — не больше 20 первых сообщений в сутки (отметьте номер новым в кабинете).

Что дальше

Была ли статья полезна?