Всем привет!
Два месяца назад я и мой знакомый (для краткости, назовем его Илья) запустили свой стартап.
Пффф… Скажите вы. Каждый день кто-то что-то запускает. Кто-то запускает в одиночку. Некоторые кучкуются в команды. У кого-то есть деньги на разработку\маркетинг, кто-то предлагает долю, пост-оплату, опционы. Все крутятся как могут и ищут партнеров также.

У нас не было денег, был лишь опыт и 2 недели до первых продаж.
Под катом я расскажу о том, с чем мы столкнулись и как заработали миллион в кризис
Мне лишь хочется поделиться тем опытом, который приобрели мы.
Точнее поделиться тем опытом, который позволил запустить это все за 2 недели.
Серьезно! Я не шучу. От первого митинга до запуска проекта — 2 недели!
Неделя на разработку и раскрутку в соц. сетях и неделя на продажи.
В ходе повествования я буду упускать малозначимые (или очевидные, которые можно найти в документациях) детали и делать акцент лишь на определенных тонкостях. Также будут встречаться конфиги и примеры кода.
Когда начался карантин многие сферы бизнеса начали потихоньку проседать. Это затронуло и сферу Ильи, а именно — «танцы и хореография». Он владеет большим количеством танцевальных школ. Такая же беда настигла и фитнес. И там люди начали выкручиваться путем проведения онлайн тренировок. Так у него появилась идея провести танцевальный лагерь онлайн (по полной аналогии с оффлайн лагерями). С чем он и пришел ко мне.
В конечном итоге команда сформировалась только из 2х людей:
Если не лукавить, то этот список можно дополнить наемными сотрудниками (дизайнер, смм, переводчик, юрист), но это уже мелочи жизни. Мы это относим к разовым расходам. И эти расходы небольшие.
Смысл танцевального лагеря был прост. Есть 12 преподавателей и мероприятие из 3х дней. По 4 класса в день. Необходимо было создать платформу, где пользователь смог бы зарегистрироваться, купить себе доступ в лагерь. Была также информация о преподавателях, лайнап\расписание, некая форма для обратной связи. Ну и самое интересное — необходимо было организовать стриминг артистов, кто также сидит дома на карантине. Почему мы не выбрали решения типа instagram — бан за фоновую музыку. Решения типа Zoom отмели за ненужностью интерактива + слишком дорого + необходимость установки софта.
Отдельный нюанс в том, что преподаватели из Европы и США также находятся на карантине. Это накладывало определенные ограничения на техническую часть.
И еще один нюанс — это должен быть законченный продукт. Люди должны были видеть нечто красивое и понимать, что они покупают, что они получат. Почему им это нужно.
Мы хотели сделать MVP для запуска первого лагеря и в дальнейшем проводить их еще и еще. Нужно проверить гипотезу, что у людей есть потребность в данном сервисе. И дело не только в карантине. Как взрослые люди мы предъявляли ряд требований к нему:
Панель администратора была сделана с использованием Sonata Admin Bundle. Настраивается этот зверь быстро, удобен в использовании. Не надо тратить время на разработку какой-либо кастомной панели.
Теперь остановимся чуточку подробнее на разных нюансах.
Сразу хочется отметить, что описанные ниже вещи это лишь список рекомендаций (советов), как сделать быстро и недорого. Многое из написанного для кого-то будет очевидным, а для кого-то нет.
Авторизация через SMS это самый большой пылесос для ваших денег. Если вы запускаете стартап — подумайте 10 раз о том, нужно ли оно вам. Представьте, что к вам пришли 1000 пользователей. А теперь умножьте на среднюю цену SMS в Европе\США и добавьте это в себестоимость вашего проекта. Также вы испытаете ряд проблем по согласовании имени отправителя. Да к тому же это еще и долго (в некоторых странах этот процесс длится от 2-3х недель и более). А от стандартного имени отправителя многие операторы сотовой связи блокируют SMS. Мало приятного. Поэтому мы использовали авторизацию через почту\соц. сети.
Очень быстро авторизацию через соц.сети можно интегрировать, добавив в проект библиотеку HWIOAuthBundle.
Я более чем уверен, что в других языках и фреймворках также имеются подобные.
Она хорошо документирована и есть различные примеры.
Письма мы сразу стали отсылать через очередь. Я считаю, что даже для маленьких проектов это нужно взять за основу. Делается это очень быстро. Да и кто знает, может через месяц ваш стартап выстрелит и там будет х10 пользователей.
Про NATS есть статьи на хабре в том числе.
Есть готовые библиотеки как для php, так и для Go (и множества других языков)
Т.е. получилась связка PHP -> NATS -> GO -> Amazon SES
При помощи docker-compose устанавливаем NATS:
Добавляем в composer.json пакет для работы с ним:
Вот пример функции для отправки почты:
У себя в проектах я взял за привычку писать множество фабричных методов, и вызывать функцию отправки уже из суперкласса уведомлений.
Это добавляет гибкости. А ведь стартап должен придерживаться этого при разработке.
Еще это удобно для отладки.
Ведь можно добавить некий класс Dummy с функцией:
В Go подписаться на очередь можно примерно так:
объявив перед этим:
Для отправки, как я писал выше, мы использовали Amazon SES.
Почему?
Еще один очень важный нюанс.
Так как у нас не было отдельного отдела поддержки, то мы сразу добавили переадресацию с почты поддержки на наши личные. Это позволило оперативно реагировать на вопросы пользователей.
Теперь ваши пользователи могут БЫСТРО и ЛЕГКО авторизоваться. Самое время получить с них заветные денежки за билеты на ваше мероприятие.
С точки зрения кода система платежей устроена довольно-таки просто. Есть суперкласс для генерации ссылки, и опять же множество фабричных методов. Они умеют сгенерировать ссылку для оплаты, и обработать колбэк. А также запустить пост-продажные хуки (уведомление по почте + добавление доступа к мероприятию).
Продукт наш был ориентирован на Европу, поэтому и цены были в Евро.
С какими трудностями же мы столкнулись:
Так получилось, что мы сначала принимали оплаты в рублях. После согласования платежного шлюза — переключились на евро. Это немного снизило конверсию на старте.
Если с авторизацией все просто, да и биллинг можно своими силами прикрутить. Такие задачи часто встречаются у средне-статистического разработчика. То вот со стримингом все не так однозначно, как кажется на первый взгляд. Что же не так?
В качестве решения мы выбрали связку larix broadcastr app -> nginx + rtmp -> cdn -> client
В ходе поиска, приложение Larix Broadcaster для вещания RTMP с мобильных устройств оказалось самым надежным и стабильным. На отдельном маленьком виртуальном сервере мы подняли nginx + rtmp для рестриминга дальше в CDN.
Данная схема является самой дешевой. Намного дешевле, чем пользоваться услугами готовых стриминговых платформ. У вас нет необходимости покупать какой-либо премиум\голд\супер аккаунт на месяц\год. Вы платите только за потраченный трафик. Это было идеальным решением в нашей ситуации.
Там же и разворачивали зеркально картинку.
На фронт части мы же использовали простой html5 плеер, сконфигурированный с учетом мобильных устройств и их особенностей:
Решение оказалось очень стабильным и удобным.
После мероприятия мы подсчитали процент жалоб на качество — это были 2%. И в основном люди, у которых старые стационарные компьютеры или устаревшее ПО (браузеры).
Что же в итоге?
Мы провели 2 онлайн лагеря.
Получили только положительный фидбэк от первых пользователей.
В тяжелое время карантина это было именно то, что нужно людям дома.
Общая выручка составила более 1.000.000 рублей.
Если немножечко поговорить о кастдеве, то ориентируясь на фидбэк первого лагеря, мы доработали систему
Какие выводы можно сделать:
Надеюсь, что данная статья кому-нибудь покажется полезной, а кого-нибудь мотивирует.
Два месяца назад я и мой знакомый (для краткости, назовем его Илья) запустили свой стартап.
Пффф… Скажите вы. Каждый день кто-то что-то запускает. Кто-то запускает в одиночку. Некоторые кучкуются в команды. У кого-то есть деньги на разработку\маркетинг, кто-то предлагает долю, пост-оплату, опционы. Все крутятся как могут и ищут партнеров также.

У нас не было денег, был лишь опыт и 2 недели до первых продаж.
Под катом я расскажу о том, с чем мы столкнулись и как заработали миллион в кризис
Мне лишь хочется поделиться тем опытом, который приобрели мы.
Точнее поделиться тем опытом, который позволил запустить это все за 2 недели.
Серьезно! Я не шучу. От первого митинга до запуска проекта — 2 недели!
Неделя на разработку и раскрутку в соц. сетях и неделя на продажи.
В ходе повествования я буду упускать малозначимые (или очевидные, которые можно найти в документациях) детали и делать акцент лишь на определенных тонкостях. Также будут встречаться конфиги и примеры кода.
Кто мы?
Когда начался карантин многие сферы бизнеса начали потихоньку проседать. Это затронуло и сферу Ильи, а именно — «танцы и хореография». Он владеет большим количеством танцевальных школ. Такая же беда настигла и фитнес. И там люди начали выкручиваться путем проведения онлайн тренировок. Так у него появилась идея провести танцевальный лагерь онлайн (по полной аналогии с оффлайн лагерями). С чем он и пришел ко мне.
В конечном итоге команда сформировалась только из 2х людей:
- техническая часть (я)
- маркетинг + управление (Илья)
Если не лукавить, то этот список можно дополнить наемными сотрудниками (дизайнер, смм, переводчик, юрист), но это уже мелочи жизни. Мы это относим к разовым расходам. И эти расходы небольшие.
Что мы делали?
Смысл танцевального лагеря был прост. Есть 12 преподавателей и мероприятие из 3х дней. По 4 класса в день. Необходимо было создать платформу, где пользователь смог бы зарегистрироваться, купить себе доступ в лагерь. Была также информация о преподавателях, лайнап\расписание, некая форма для обратной связи. Ну и самое интересное — необходимо было организовать стриминг артистов, кто также сидит дома на карантине. Почему мы не выбрали решения типа instagram — бан за фоновую музыку. Решения типа Zoom отмели за ненужностью интерактива + слишком дорого + необходимость установки софта.
Отдельный нюанс в том, что преподаватели из Европы и США также находятся на карантине. Это накладывало определенные ограничения на техническую часть.
И еще один нюанс — это должен быть законченный продукт. Люди должны были видеть нечто красивое и понимать, что они покупают, что они получат. Почему им это нужно.
Чего мы хотели?
Мы хотели сделать MVP для запуска первого лагеря и в дальнейшем проводить их еще и еще. Нужно проверить гипотезу, что у людей есть потребность в данном сервисе. И дело не только в карантине. Как взрослые люди мы предъявляли ряд требований к нему:
- Решение должно быть стабильным. Никаких 500-ых ошибок, зависших транзакций. Тикеты в саппорт нам не нужны. Все должно работать без нашего участия
- Решение должно быть гибким и масштабируемым. Сегодня у нас 100 человек. Завтра 10000. Письма должны ходить. Стриминг должен выдерживать любую нагрузку. В случае смены платежного шлюза например, не должно быть временного лага в принятии оплаты
- Интерфейс должен быть понятен как для пользователей, так и преподаватели сидя дома должны суметь организовать стриминг с мобильного телефона
- Мы хотели получить прибыль и в дальнейшем масштабировать наши доходы. Т.о. мы по максимуму минимизировали расходы
Что мы использовали?
- backend на symfony
- frontend на jquery + vue
- упаковка ресурсов через webpack
- очередь nats
- небольшой микросервис на Go для отправки писем
- хранилище Minio для статического контента
- redis для различных подзадач
Панель администратора была сделана с использованием Sonata Admin Bundle. Настраивается этот зверь быстро, удобен в использовании. Не надо тратить время на разработку какой-либо кастомной панели.
Теперь остановимся чуточку подробнее на разных нюансах.
Сразу хочется отметить, что описанные ниже вещи это лишь список рекомендаций (советов), как сделать быстро и недорого. Многое из написанного для кого-то будет очевидным, а для кого-то нет.
Авторизация
Авторизация через SMS это самый большой пылесос для ваших денег. Если вы запускаете стартап — подумайте 10 раз о том, нужно ли оно вам. Представьте, что к вам пришли 1000 пользователей. А теперь умножьте на среднюю цену SMS в Европе\США и добавьте это в себестоимость вашего проекта. Также вы испытаете ряд проблем по согласовании имени отправителя. Да к тому же это еще и долго (в некоторых странах этот процесс длится от 2-3х недель и более). А от стандартного имени отправителя многие операторы сотовой связи блокируют SMS. Мало приятного. Поэтому мы использовали авторизацию через почту\соц. сети.
Очень быстро авторизацию через соц.сети можно интегрировать, добавив в проект библиотеку HWIOAuthBundle.
Я более чем уверен, что в других языках и фреймворках также имеются подобные.
Она хорошо документирована и есть различные примеры.
Найти ее можно вот тут
Письма мы сразу стали отсылать через очередь. Я считаю, что даже для маленьких проектов это нужно взять за основу. Делается это очень быстро. Да и кто знает, может через месяц ваш стартап выстрелит и там будет х10 пользователей.
Про NATS есть статьи на хабре в том числе.
Есть готовые библиотеки как для php, так и для Go (и множества других языков)
Т.е. получилась связка PHP -> NATS -> GO -> Amazon SES
При помощи docker-compose устанавливаем NATS:
version: "2" services: nats: container_name: nats-mq image: nats networks: - "back" ports: - "127.0.0.1:4222:4222" - "127.0.0.1:6222:6222" - "127.0.0.1:8222:8222" entrypoint: "/gnatsd -c gnatsd.conf --auth SECRET --debug" restart: always networks: back: driver: "bridge"
Добавляем в composer.json пакет для работы с ним:
... "repejota/nats": "^0.8.6" ...
Вот пример функции для отправки почты:
public function email($emails, $body, $subject) { if (empty($emails)) { return false; } $prefix = getenv('NATS_PREFIX'); $encoder = new JSONEncoder(); $options = new ConnectionOptions([ 'token' => getenv('NATS_TOKEN') ]); $client = new EncodedConnection($options, $encoder); try { $client->connect(); $client->publish( $prefix . 'email', [ 'Emails' => $emails, 'HtmlBody' => $body, 'TextBody' => strip_tags($body), 'Subject' => $subject, 'Sender' => 'info@' . getenv('DOMAIN_NAME') ] ); } catch (\Exception $exception) { return false; } return true; }
У себя в проектах я взял за привычку писать множество фабричных методов, и вызывать функцию отправки уже из суперкласса уведомлений.
Это добавляет гибкости. А ведь стартап должен придерживаться этого при разработке.
Еще это удобно для отладки.
Ведь можно добавить некий класс Dummy с функцией:
public function email($emails, $body, $subject) { $time = time(); file_put_contents(SRC_PATH . '../var/dummy/email' . $time . '.html', $body); return true; }
Общий интерфейс оставлю здесь
interface NotificationInterface { /** * Инициализируем нотификатор * * init storage * @void */ public function init(); /** * Синхронные ли уведомления * * @return bool */ public function isSync(); /** * Отправляет СМС * * @param $phone * @param $message * @return bool */ public function sms($phone, $message); /** * Отправляет письмо * * @param $email * @param $body * @param $subject * @return bool */ public function email($email, $body, $subject); }
В Go подписаться на очередь можно примерно так:
opts := nats.Options{ Url: ..., Token: ..., } nc, err := opts.Connect() if err != nil { log.Error("error in nats connection", err) } c, err := nats.NewEncodedConn(nc, nats.JSON_ENCODER) if err != nil { log.Error("error in nats encoding", err) } defer c.Close() defer nc.Close() c.Subscribe("email", func(p *EmailInput) { p.sendEmail() })
объявив перед этим:
type EmailInput struct { Emails []*string `json:"Emails"` HtmlBody string `json:"HtmlBody"` TextBody string `json:"TextBody"` Subject string `json:"Subject"` Sender string `json:"Sender"` } func (email *EmailInput) sendEmail() { ... }
Для отправки, как я писал выше, мы использовали Amazon SES.
Почему?
- администрировать свой почтовый сервер очень накладно и это отнимет время
- это готовое решение с хорошим бесплатным лимитом
- легко настраиваются DKIM + SPF (иначе письма будут улетать в спам, конверсия будет падать)
Мануал по настройке SPF здесь
Еще один очень важный нюанс.
Так как у нас не было отдельного отдела поддержки, то мы сразу добавили переадресацию с почты поддержки на наши личные. Это позволило оперативно реагировать на вопросы пользователей.
Теперь ваши пользователи могут БЫСТРО и ЛЕГКО авторизоваться. Самое время получить с них заветные денежки за билеты на ваше мероприятие.
Биллинг
С точки зрения кода система платежей устроена довольно-таки просто. Есть суперкласс для генерации ссылки, и опять же множество фабричных методов. Они умеют сгенерировать ссылку для оплаты, и обработать колбэк. А также запустить пост-продажные хуки (уведомление по почте + добавление доступа к мероприятию).
Общие интерфейсы для хуков и платежных систем оставляю здесь
interface HookInterface { /** * @return string */ public function getName(); /** * @param Transaction $transaction * @param array $params */ public function run(Transaction $transaction, array $params); }
interface SystemInterface { /** * @return string */ public function getName(); /** * @param Transaction $transaction * @param string|null $redirect * @return null */ public function createLink(Transaction $transaction, $redirect = null); /** * @param Transaction $transaction * @return string */ public function createWidget(Transaction $transaction); /** * @return string */ public function createWidgetAssets(); /** * @param $request * @param $em * @return PayResponse */ public function handleCallback(Request $request, ObjectManager $em); }
Продукт наш был ориентирован на Европу, поэтому и цены были в Евро.
С какими трудностями же мы столкнулись:
- Можно по пальцам пересчитать платежные шлюзы, которые имеют валютные терминалы. Что это? Это такая штука, которая позволяет принимать оплату именно в ЕВРО, а не рублях. Почему это так важно? Да потому что Европейцы настороженно относятся к Русским, очень много мошенников. Рубли при оплате будут снижать конверсию и добавлять тикеты в саппорт, где вам придется объяснять кто вы и что вы.
- Согласование валютного терминала занимает время. Готовьтесь, что это будет от 5 дней и выше. На практике около 10. Потому что вас тщательно проверят, да и в карантин Европейцы работали неохотно. Также вопросы возникнут и со стороны нашего отечественного банка, зачем вам это потребовалось
- 3d secure никто не отменял (SMS, которая приходит с кодом подтверждения оплаты). Поэтому если у клиента в Европе банк не поддерживает эту возможность, то оплатить он УВЫ не сможет. На этот случай придется билить клиента ручками на PayPal
- Согласование сайта происходит только тогда, когда весь контент уже присутствует на нем. Т.е. сайт полностью готов, в том числе все юридический документы типа оферты. Т.к. у нас был не интернет-магазин и в целом нетипичное решение — мы обратились к юристам для составления оферты под наш продукт и последующий её перевод
Так получилось, что мы сначала принимали оплаты в рублях. После согласования платежного шлюза — переключились на евро. Это немного снизило конверсию на старте.
Организация стриминга
Если с авторизацией все просто, да и биллинг можно своими силами прикрутить. Такие задачи часто встречаются у средне-статистического разработчика. То вот со стримингом все не так однозначно, как кажется на первый взгляд. Что же не так?
- Мы никогда не имели с ним дело. Нужен был сервер + клиент для преподавателей
- Картинку необходимо зеркалить, т.е. делать вертикальное отображение (особенности танцевальных видео)
- Мы точно не знали сколько будет участников в лагере. И покупать где-либо (в готовых стриминговых решениях) аккаунт на месяц\год\кол-во трафика мы пока не могли. Это сожрало бы всю прибыль
- Нужна была защита iframe от воровства контента хотя бы по рефереру
В качестве решения мы выбрали связку larix broadcastr app -> nginx + rtmp -> cdn -> client
В ходе поиска, приложение Larix Broadcaster для вещания RTMP с мобильных устройств оказалось самым надежным и стабильным. На отдельном маленьком виртуальном сервере мы подняли nginx + rtmp для рестриминга дальше в CDN.
Данная схема является самой дешевой. Намного дешевле, чем пользоваться услугами готовых стриминговых платформ. У вас нет необходимости покупать какой-либо премиум\голд\супер аккаунт на месяц\год. Вы платите только за потраченный трафик. Это было идеальным решением в нашей ситуации.
Там же и разворачивали зеркально картинку.
Конфигурация для рестриминга лежит здесь
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; include _root.conf; include _stat.conf; } include _rtmp.conf;
server { listen 80; server_name localhost; location / { root html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }
server { listen 8080; auth_basic "Restricted Area"; auth_basic_user_file .htpasswd; location / { root www; } location /stat { rtmp_stat all; rtmp_stat_stylesheet stat.xsl; } location /stat.xsl { root www; } }
rtmp { server { listen 1935; chunk_size 4096; application your-custom-hash-here { live on; record off; allow publish all; allow play all; push rtmp://localhost/cdn; } application cdn { allow publish 127.0.0.1; deny publish all; live on; record off; exec ffmpeg -i rtmp://localhost/cdn/$name -vf "hflip" -crf 30 -preset ultrafast -acodec aac -strict experimental -ar 44100 -ac 2 -b:a 128k -vcodec libx264 -x264-params keyint=60:no-scenecut=1 -r 30 -b:v 2000k -f flv rtmp://rtmp.cdnhost.abc; } } }
На фронт части мы же использовали простой html5 плеер, сконфигурированный с учетом мобильных устройств и их особенностей:
videojs($('#video')[0], { controls: true, autoplay: false, preload: 'auto', poster: '/static/splash-en.jpg', language: 'en', muted: true, html5: { hls: { overrideNative: !videojs.browser.IS_SAFARI } } });
Решение оказалось очень стабильным и удобным.
После мероприятия мы подсчитали процент жалоб на качество — это были 2%. И в основном люди, у которых старые стационарные компьютеры или устаревшее ПО (браузеры).
Заключение
Что же в итоге?
Мы провели 2 онлайн лагеря.
Получили только положительный фидбэк от первых пользователей.
В тяжелое время карантина это было именно то, что нужно людям дома.
Общая выручка составила более 1.000.000 рублей.
Если немножечко поговорить о кастдеве, то ориентируясь на фидбэк первого лагеря, мы доработали систему
- Сделали возможность покупки билета на один день (а не только полный билет) — это подняло выручку, т.к. многие не хотели покупать полный билет, а хотели купить лишь мастер-класс одного или двух преподавателей
- Сделали простой чат около видео-плеера на socket.io — это добавило интерактива в уроки и позволяло получить обратную связь в реальном времени
- Т.к. лагерь международный и многие люди путались в часовых поясах — мы добавили возможность изменять таймзону в расписании и смотреть расписание по своему часовому поясу
Какие выводы можно сделать:
- Не обязательно иметь стартовые инвестиции и огромную команду, чтобы проверить идею
- Ведите разработку гибко, прислушивайтесь к вашим пользователям. Добавляйте только то, что реально нужно людям
- Не пишите сразу огромный монолит с кучей функций, он не нужен для проверки гипотезы. Не копите тех. долг. Умейте находить баланс между «плохим» кодом, и «избыточной» функциональностью. Используйте каждый инструмент по назначению
- Используйте опыт своих знакомых. Большинство задач ведь уже решено. Достаточно лишь найти нужные инструменты, провести исследования, объединить их в единую систему
Надеюсь, что данная статья кому-нибудь покажется полезной, а кого-нибудь мотивирует.
