- Регистрация
- 14.08.2020
- Сообщения
- 654
- Благодарностей
- 761
- Баллы
- 93
Привет, из Одессы! Хочу поделиться опытом и методой мышления, когда вы строите какой-то IT продукт. В моём случае - это сервис валидации email баз.
Думаю не секрет, что это одна из самых древних услуг в интернете. Рынок на первый взгляд сформирован и поделен, и чего вообще туда соваться?)
0. Терпение и труд, всё перетрут и пруфы добудут
Мы принимаем оплаты 4-мя основными методами, покажу стату только из Yandex кошелька, +- такое же кол-во залетело через другие 3.
Фактически ушло 3,5 месяца, что бы пробить 10 000$/месяц. Медленно это или быстро - решать вам, как по мне получилось беньч.
1. Гипотеза ценности, гипотеза роста и модель монетизации
Кто знаком с Lean, понимают важность заложения этих фундаментальных вещей на самом старте, особенно на старых рынках. Именно на них самый высокий порог входа
и зайти только с демпингом тут не выйдет, это не tv приставки продавать.
Очевидно что ценность сервиса валидации это качество чека, но таких предложений куча, только вот их стоимость зашкаливает. В среднем, у хорошего сервиса проверить 1кк
Почт обойдётся в 250-350 у.е. Всё что дешевле - треш, которого можно добиться самостоятельно, скачав крякнутую программу.
Такие большие ценны дают понимание, что валидация, это недешевое удовольствие в плане расходников. Все сервисы делают чек реал-тайм, вот и задирают ценники так.
Бинго - гипотеза ценности, которая заключается в большой бд прочеканных email, в которой почты перевалидируются только раз в 2-3 месяца, тем самым кардинально меняется себестоимость. Дополнительная плюшка в будущем - это скорость проверки,
а скорость это и есть гипотеза роста, в этом бизнесе. Перед тем как стартовать, мы скупили сотни дампов, тысячи списков нашли и скачали в интернете. Пока делали техническую часть и подготовительные работы, шла валидация всего этого дела.
Вот только одна загвоздка не давала мне покоя, что делать с почтами которых не нашлось в базе?
Со стороны владельца сервиса, уже звучит не плохо, не так ли? А на деле, фраза: "ага, что бы вы сами слали по моей базе" стала хитом сначала. Подстегнуть людей грузить свои базы могли лишь халява и пруфы, в виде отзывов. В обмен за честный отзыв мы давали 1кк лимитов, что и вам советую делать на старте. Без пруфов, считай что у тебя ничего нет, никто не хочет быть первым кинутым.
В общем, так и сформировалась элегантная модель монетизации, от которой конкуренты ахнут. Если точнее, это мы начали делать бесплатно то, за что они берут деньги.
Итог: Человек платит только за те почты, которые нашлись в базе, что не нашлось - валидируем бесплатно. Как по мне, очень изящно, так как мы несём затраты на чек почты только 1 раз в квартал, дальше эта почта многоразово отнимает лимиты и соответственно даёт доход.
2. Используем предыдущий опыт, не повторяем ошибок и устанавливаем ограничения.
Предпринимательство штука, как бы так, сказать по деликатней - ироничная Очень мало фирм, живут действительно долго, когда сходу свезло.
Когда я стартую новое, хочу получить бизнес на года, но при этом отношусь как к капризному ребёнку на начальных этапах. Так уже вышло что предыдущая попытка запустить IT продукт - это мертворождённый ребёнок,
в которой я не понимал многих подводных камней. Даже имея крутую идею, добыв около 450к$ инвестиций я потерпел поражение. Ошибок было много, но есть несколько, которые фундаментально портили всё:
- Люди, которых ты берешь в долю.
- Идя к целям, забуриваешься в средство так сильно, что оно становилось целью.
- Без уверенности в достоверности MVP, не обрастай функционалом как у больших парней.
1. Лучше иди один, если есть хоть капля сомнения, хоть и выглядит что этот чел. именно то что нужно. По итогу может оказаться, что будете работать за одного или больше человек.
Если коротко : Придерживайся правила не работать с муд*****, делая несколько проверок на вшивость. С этими людьми тебе предстоит провести день у день, ближайшие года...
2. Если вы такой же, в этом плане, как я - вот если есть проблема она муляет, полностью фокусируя на себе, все твои мысли. И каждый раз погружаясь ниже, ожидая развязки, появляются новые заборы/технологии/методы.
Мне очень помогло начать работать в спринтах, забыл про эту проблему.
3. По факту, MVP в моём понимании - это когда хотя бы 100 человек заплатило за набор фич, которые закрывают их боль. При чем несколько раз, то бишь 100 человек и 200 фактических оплат.
Пройдя этот рубеж, неопределённость падает до минимума. Ты отчетливо видишь приоритет разработки новых функций или починки существующих.
3.Из этого всего установили амбициозные ограничения, что бы не вышло нового факапа:
1. MVP должно быть пройдено, не потратив, ни копейки на программистов.
2. Поддержка и общение с пользователями только в телеграм, отказавшись от скайпов, почты и другой дико отвлекающей хрени.
3. Поддержку пользователей делать самому, опять же, как минимум до прохождения MVP, если ситуация позволяет - как можно дольше.
4. Самый удачный момент старта, каков он?
Из предыдущего долгостроя, я осознал ответ на этот вопрос. Самый удачный момент - это вчера. Чем быстрее начнёшь получать деньги, тем больше вероятность, что всё получиться.
Оттягивая момент запуска, в угоду перфекционизму - это фатальная ошибка. Лучше дёшево и с багами, чем дальше делать работу ради работы, находясь в прострации.
Первые оплаты - это и мотивация, и приоритеты, а самое главное отзывы, как хорошие, так и плохие.
-------------------------------------------------------------------------------------------------------------------
Что у нас было на момент старта:
Собрали и отвалидировали 1.5 миллиарда уникальных строк;
База данных MySQL развернутый с помощью докера, вместе с веб панелькой adminer (Файл для развертывания приложу);
~150 разных шаблона, каждый чекает свой почтовый биг. Что то на POST/GET, что то на c#, что то на браузере;
Серверная версия телеграм бота на докер контейнере, позволяющая заливать в бота файлы до 2-ух гб, вместо 20 Мб(Файл для развертывания приложу);
Шаблон непосредственно диалогов телеграм бота, который отвечает клиенту;
Сохранение файлов в Microsoft Azure Blob с помощью powershell, из отдельного шаблона зенки;
Старт:
И вот, 31-го декабря, поздравляю Семёна с НГ и решаем стартовать завтра. Так что бы точно всё пошло - ставим прайс стартовый 3$ за 1кк почт.
Барабанная дробь, спустя пару часов как опубликовали по форумам топик, первая покупка на 9$. Радости полные штаны, при чем в тот вечер зашло еще пару оплат.
И тут нагрузка повалила... Получали в течении 2 недель, каждый день оплат на 20 миллионов почт в среднем. Мы себе такие думаем, пф, та мелочи, мы ж уже полтора лярда чекнули.
Однако мы не учли, что спешки же не было. И вот каждый день 20-30 человек, сначала аккуратно, а потом жестко требуют быстрее выдать им базу.
Друг, то был фальшь старт, давай по новой
Слава Богу, удалось выполнить все задания без единого манибека, однако осталось от тех клиентов 30-40%, которые пользуются по сей день . В какой-то момент мы просто перестали брать деньги, поясняя что не справляемся. Мы решили доделать под 0 все базы и переделывать архитектуру.
Фактически мы остановили кораблик на 10 дней, за время которых переехали на PostgreSQL(Файл для развертывания приложу), ибо муська уже задыхалась, а то был только старт.
Пока я переделывал запросы под синтаксис постгре, Сеня правил баги и готовил идеи реализации скоростного, универсального чекера.
Фактически этот "стресс тест" дал нам понимание и полностью убрал неопределённость. И самое главное сложилась юнит экономика, которая показала какие цены надо ставить.(Быстрый шаблон расчета unit экономики , Комплексные расчеты с графиками)
Так что бы было рентабельно, нам пришлось поставить цену 30$ за 1 миллион почт, которая установлена по сей день.
Затишье перед бурей
Как и следовало ожидать, после х10 к стоимости, ажиотажа сходу не было и Слава Богу. Но стабильно прилетала минимум 1 оплата, которая давала нам порцию дофамина на день
По сути образовалось окно в полтора месяца, за который мы успели:
- Реализовать мега шустрый универсальный чекер, на node.js. При том, что ноду вообще не знал, фокус на изучении и реализации, дал восхитительный результат, на тот момент - 20 миллионов почт в сутки.
- Семён перепилил самого телеграм бота, ибо тормозил на пост/гет запросах ужас. Попутно внося корректировки и добавляя месседжи в диалоги бота. К этому моменту, у нас набралось 10 супер активных пользователей, которые отсылают в сутки 1-2кк писем и больше. По их же словам, говорили как круто, как будто под него продукт делается. А по факту, он становился всё более удобным и быстрым для всех.
- Сделали сценарий с авторизацией через бд, подсказки, ошибки и самое главное, это генерация аккаунта, с 10 000 лимитов на потестить.
- Настроили систему бекапов, сделали классное логирование как для бд, так и в шаблонах
- Подняли распределенный кластер PostgreSQL, ибо полностью в Azure это слишком дорого.
- Разместились на всех релевантных форумах СНГ
- Допили сайт - Ссылка
- Настроили AdWords 2 кампании КМС + бартером сделали видео гайд и запустили Youtube кампанию с ним
- RESTfull API на node.js, с основными эндпоинтами, которые необходимы для работы по апи.(Шаблон приложу)
- Набрали мощного железа
- Провели 32 линии оптики, по 1гб каждая
И это всё, было сделано 2-умя людьми. Очередной раз доказывая - за мотивированный сотрудник круче 10-рых на зарплате.
Ну и, как видно по первому скрину, вселенная нам вернула за этот стек работ, добротное количество дублонов.
Огромное спасибо пользователям, которые терпеливо смотрят на баги и тыкают их нам в нос! Пожалуйста, продолжайте в том же духе
В заключение, хочу вам сказать одну вещь - делать массовый продукт, который даёт ценность его пользователям, это жирный жир!
Надеюсь, я замотивировал вас включать смекалку, искать новые подходы и делать полезный софт с помощью Zennoposter
---------------------------------------------------------------------------------------------------------------------------------------------------------
Прочёл статью про логирование и стало стыдно. Дополню статью сценариями работы telegram бота, на зенке. Использовать будем c#
Шаг #1 разворачиваем серверного телеграм бота, для выполнения у вас должен быть установлен docker для винды , или docker и docker-compose на linux.
Использование локального сервера API telegram ботов
Вы можете запустить его локально и отправлять запросы на свой сервер вместо https://api.telegram.org. Если вы переключитесь на локальный сервер API ботов, ваш бот сможет:
- Скачивайте файлы без ограничений по размеру.
- Загружайте файлы размером до 2000 МБ.( Используя стандартную версию, вы можете загрузить максимум 20 МБ )
- Загрузите файлы, используя их локальный путь и схему URI файла .
- Используйте URL-адрес HTTP для веб-перехватчика.
- Используйте любой локальный IP-адрес для веб-перехватчика.
- Используйте любой порт для веб-перехватчика.
- Установите max_webhook_connections до 100000.
- Получите абсолютный локальный путь как значение поля file_path без необходимости загружать файл после запроса getFile .
Сохраняем этот код в файл docker-compose.yml , по пути C:\tg:
version: '3.8'
volumes:
server-data:
driver: local
services:
server:
image: ghcr.io/bots-house/docker-telegram-bot-api:latest
restart: always
environment:
# получить можно тут https://core.telegram.org/api/obtaining_api_id
TELEGRAM_API_ID: 1111111
TELEGRAM_API_HASH: 11aa1a11111111a1aa128add52a630aa
command:
- --local
# рабочая папка в контейнере
- --dir=/var/lib/telegram-bot-api
# включить логирование, выключайте в продакшене.
- --verbosity=1
volumes:
# Подвязываем папку винды к рабочей папке бота
- C:\tg:/var/lib/telegram-bot-api
ports:
# порт доступа API на порту 8081
- 8081:8081
#- 80:80
- cd C:\tg
- docker-compose up -d
Готово! Наш бот онлайн.
Получаем последнее сообщение из бота, преобразовываем в JSON:
lock(CommonCode.bot)
{
project.Variables["bot_token"].Value="111111111:AAAAAaaaaaAaaaaaAaAaA-AAwqYjAbn9A_a";
project.Variables["tgapiip"].Value="192.168.0.23";
project.Variables["dbIp"].Value="localhost";
project.Variables["get_data"].Value = ZennoPoster.HttpGet("http://"+project.Variables["tgapiip"].Value+":8081/bot"+project.Variables["bot_token"].Value+"/getUpdates?offset="+project.Variables["update_id"].Value,"", "UTF-8",ZennoLab.InterfacesLibrary.Enums.Http.ResponceType.BodyOnly);
if(project.Variables["get_data"].Value.Contains("{\"ok\":true,\"result\":[]}"))
return null;
else
{
Root obj = JsonConvert.DeserializeObject<Root>(project.Variables["get_data"].Value);
project.GlobalVariables.SetVariable("[email protected]", "lastMid", obj.result[0].update_id);
var list = project.Lists["сессии"];
if(list.Contains(obj.result[0].update_id.ToString()))
return null;
if(obj.result.Count()!=0)
{
project.Variables["count"].Value=Convert.ToString(obj.result.Count());
project.Variables["count"].Value=Convert.ToString(Convert.ToInt32(project.Variables["count"].Value)-1);
var update_id=obj.result[Convert.ToInt32(project.Variables["count"].Value)].update_id;
project.Variables["update_id"].Value=(update_id+1).ToString();
// Текст для обработки
string text = project.ExecuteMacro(project.Variables["get_data"].Value);
var errorIfEmpty = true;
var regexGroups = Macros.TextProcessing.Regex(text, "(?<=\"message\":)\\{.*}}");
if (regexGroups.Count == 0)
{
if (errorIfEmpty) throw new Exception();
return null;
}
else
{
list.AddRange(regexGroups.Select(g => g.FirstOrDefault()));
}
}
else
return null;
}
}
/getUpdates?offset= Затираем полученное сообщение и делаем рандомную задержку. Защищаемся от повторного выполнения, одного и того же полученного сообщения:
var post = ZennoPoster.HttpGet("http://"+project.Variables["tgapiip"].Value+":8081/bot"+project.Variables["bot_token"].Value+"/getUpdates?offset="+project.Variables["update_id"].Value,"", "UTF-8",ZennoLab.InterfacesLibrary.Enums.Http.ResponceType.BodyOnly);
Random rnd = new Random();
int timer = rnd.Next(0, 100);
Thread.Sleep(timer);
return post;
Защита от дублей ответов бота, болезнь при многопотоке.:
var id = string.Empty;
lock(CommonCode.id) {
var gbVar = project.GlobalVariables["[email protected]", "lastMid"];
id = Regex.Match(project.Variables["get_data"].Value,"(?<=message_id\":).*(?=,\"from)").ToString();
project.SendInfoToLog(id);
//gbVar.Value = Convert.ToInt32(gbVar.Value.ToString())+1;
project.Variables["currentValue"].Value = gbVar.Value.ToString();
if(project.Variables["currentValue"].Value==id)
return null;
else
{
gbVar.Value=id;
var list=project.Lists["bot_lock"];
if(!list.Contains("id"))
list.Add(id);
else
return null;
}
}
Соединяем как на следующем скрине:
Таким образом реализовали круто работающую прослушку всех входящих сообщений, от пользователя к боту.
#Шаг 3. Обрабатываем входящие сообщения
Regex + Switch разбирающие текст сообщения(это по сути обработка нажатия на кнопку клавиатуры):
string text = project.ExecuteMacro(project.Variables["get_data"].Value);
var regexGroups = Macros.TextProcessing.Regex(text, "(?<=text\":\").*?(?=\")", "0");
project.Variables["command"].Value = regexGroups[0].FirstOrDefault();
switch (project.Variables["command"].Value)
{
case "/start":
project.Variables["switch"].Value = "start";
break;
case @"\ufffd Create a new Task":
project.Variables["switch"].Value = "30";
break;
case @"\ud83d\udccb Limits":
project.Variables["switch"].Value = "31";
break;
case @"\ud83d\udcc8 Tasks list":
project.Variables["switch"].Value = "32";
break;
case @"\u2692 Support":
project.Variables["switch"].Value = "33";
break;
case @"\ufffd Buy Limits":
project.Variables["switch"].Value = "34";
break;
case @"\ud83d\udcc8 Load test":
project.Variables["switch"].Value = "35";
break;
case "API":
project.Variables["switch"].Value = "50";
break;
default:
// project.Variables["tovar_category"].Value = "300";
break;
}
31 соединяем со следующим кубиком кода C#:
Кнопка отстаток лимитов - Достаём из базы остаток лимитов, формируем и делаем ответ:
// Работа с базой данных
project.Variables["limits"].Value = ZennoPoster.Db.ExecuteQuery("SELECT \"limit\"\r\nFROM \"accounts\"\r\nWHERE \"chat_id\" = \'" + project.Variables["chat_id"].Value + "\' LIMIT 1", null, ZennoLab.InterfacesLibrary.Enums.Db.DbProvider.Odbc, "Dsn=PostgreSQL35W;database=admin_s;server="+project.Variables["dbIp"].Value+";port=5432;uid=postgres;pwd=pass;sslmode=disable;readonly=0;protocol=7.4;fakeoidindex=0;showoidcolumn=0;rowversioning=0;showsystemtables=0;fetch=100;unknownsizes=0;maxvarcharsize=255;maxlongvarcharsize=8190;debug=0;commlog=0;usedeclarefetch=0;textaslongvarchar=1;unknownsaslongvarchar=0;boolsaschar=1;parse=0;lfconversion=1;updatablecursors=1;trueisminus1=0;bi=0;byteaaslongvarbinary=1;useserversideprepare=1;lowercaseidentifier=0;xaopt=1;", ",", "\r\n");
string text=string.Empty;
string mode =string.Empty;
text = "Остаток лимитов: "+project.Variables["limits"].Value;
mode = "&parse_mode=html"+"&reply_markup="+project.Variables["reply_markup"].Value; #подставляем клавиатуру из переменной
//постим в телегу сообщение
ZennoPoster.HttpGet("http://"+project.Variables["tgapiip"].Value+":8081/bot"+project.Variables["bot_token"].Value+"/sendMessage?chat_id="+project.Variables["chat_id"].Value+"&text="+text+mode,
"", "UTF-8", ZennoLab.InterfacesLibrary.Enums.Http.ResponceType.BodyOnly);
project.SendInfoToLog("кнопка запрос лимитов - "+project.Variables["login"].Value,true);
Переменная reply_markup:
{
"keyboard": [
[ "\ufffd Create a new Task"],
["\ud83d\udccb Limits", "\ud83d\udcc8 Tasks list"],
["\u2692 Support","API"],
[ "\ufffd Buy Limits", "\ud83d\udcc8 Load test"]
],
"one_time_keyboard": true,
"resize_keyboard": true
}
Готово, наш однокнопочный бот, отдающий остаток количества лимитов из бд работает.
- Тема статьи
- Способы заработка
- Номер конкурса статей
- Пятнадцатый конкурс статей
Вложения
-
12 КБ Просмотры: 241
-
569 байт Просмотры: 259
-
516 байт Просмотры: 259
-
24,3 КБ Просмотры: 216
Для запуска проектов требуется программа ZennoPoster или ZennoDroid.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...
Для того чтобы запустить шаблон, откройте нужную программу. Нажмите кнопку «Добавить», и выберите файл проекта, который хотите запустить.
Подробнее о том, где и как выполняется проект.
Последнее редактирование модератором: