私のニッチな予約ソフトウェアは月5,700ドル($5.7K)を稼ぐ
月商 $5,700 · 創業者2人 · 従業員4人 · Lviv, Lviv Oblast, Ukraine
Pavlo Grinevich(Founder, Calday)· 創業 April 2023 · 記事掲載 March 21st, 2025 · calday.com · 元の記事
2023年4月にMVPをリリース。初期は創業者の人脈やSNS(LinkedIn、Reddit)でユーザーを集め、地元の窓清掃業者が最初の有料顧客になった。その後、PPC広告で初期の顧客を獲得したが、予算と依存回避のため停止。現在はSEOとコンテンツマーケティングが主力の集客チャネルで、オーガニック検索から毎日5〜9人の新規ユーザーを獲得している。月商5,700ドルは2025年3月時点。
売値の出典: 公式サイトの料金ページ(2026-10-14取得、1ドル150円で換算)(2026-10-14 取得)
Hey guys! I'm Pavlo Grinevich, the co-founder of Calday. We started an online appointment scheduling software in 2022 that generates $5k monthly recurring revenue (MRR). Calday is a user-friendly scheduling solution for solopreneurs and small businesses like real estate, DMV, lawyers, pet sitting, and recruiting services. However, more than 50% of our audience consists of solopreneurs like financial advisors, life coaches, personal trainers, accountants, photographers, and sales professionals. Our goal is to help entrepreneurs automate their scheduling, streamline their operations, and ultimately increase sales for them. My image
Skills What skills did Pavlo Grinevich have that led to their success? custom-software calendar-integration zoom-integration Freemium and Open Source
Automation is my passion. It truly boosts my efficiency as a full-stack developer. I was overwhelmed by daily meetings and searched for a good app or software, but none of the scheduling tools I tried were up to par. They seemed outdated, more suited for the 2010s than the 2020s, and no one had a built-in calendar. That was a non-starter for me. Over the years, I explored numerous leading scheduling tools like Calendly and their counterparts, but each fell short of my expectations. A critical feature was consistently missing: a built-in calendar that could merge all my work calendars into one cohesive system. Ans a booking page for organizing new meetings. I needed this to see the big picture of my daily and weekly events. That's when the idea struck me: "Why not build it myself?" That was the birth of Calday. And my expertise in coding made me more excited about it. In 2022, my friend and I got started, and we unveiled our initial version within just eight months. It was an instant success, particularly among my tech-savvy friends grappling with the same challenges.
Frustrated by the market's gap, I forged my own path. I assembled a dynamic team of four professionals: a UX designer, two software engineers, and a marketing specialist. Together, we dedicated eight months to developing our product. Being professional developers, we didn't need much in terms of investments. We bootstrapped Calday with the little resources we had in our hands. The UX designer was already working with us on a project, and he didn't charge much. So, money saved! We only had to pay the SEO marketer (around $500) for their input in terms of website structure, sitemap creation, and integrations. By the end of April 2023, we were proud to release our MVP, a tool designed from scratch to address the specific needs that others had failed to address. First version of Homepage Our initial release was modest, lacking many features that define our current version (such as integrations with other video platforms like Zoom). Nevertheless, it resonated with like-minded individuals who appreciated our vision. We launched the free version first and asked the developer community (our colleagues) to try it. The response was satisfactory. Their feedback helped us fix some bugs and greatly motivated our team. This gave us the courage to advance our efforts, leading to the development of the current version of Calday. The development process took 10 months, culminating in a new interface that looks cozier and offers significant usability improvements. This iteration boasts a fresh design, enhanced features, and broader integrations. We also launched our premium version, which offers unlimited scheduling and integrations for growing businesses. User Dashboard (current version)
As I said, we launched our MVP version of Calday in April 2023, initially deploying it within our team. As I began organizing meetings using Calday, my colleagues and friends became curious about this new tool and started using it themselves. This widened user base provided us with a wealth of feedback, which became crucial for planning enhancements for the subsequent release. We got Calday's first customer the same way. It was a local business (window cleaning) trying to develop a dynamic company website. They booked the meeting using my scheduling page, liked what they experienced, and asked to integrate Calday scheduling with their website. That's how we got our first $10/month customer, and they are still with us. To attract more users, we first decided to spread awareness through our social media pages. LinkedIn and Reddit were helpful in this regard. People asked many questions, and some tried our free version (about 15 people). All in all, we got around 7 premium subscribers within two months. Following the release of version 2, we shifted focus towards search engine marketing. At first, we tried PPC ads, but we are currently investing in SEO and developing valuable content to attract and engage potential users.
Our initial marketing effort was centered around PPC ads, and it was successful. We operated within a modest budget, yet we consistently attracted over 190 new users monthly and started generating revenue. PPC Campaign (April 2023) We stopped the PPC ads because we didn't want to rely solely on them, and more importantly, we wanted to keep our marketing budget low. We realized that our prospective customers (solopreneurs and small businesses) leverage social media for their businesses. Therefore, we decided to launch an advertising campaign on Facebook and Instagram. But it didn't go as planned. After a month of running ads, the initial results did not meet our expectations. Consequently, we chose to pause our spending on social media marketing. After a few months, we decided to try SEO and content marketing. These strategies showed promising results. We published five blog posts and optimized our pages, and Google started to like us. Our SEO strategist suggested adding more industry pages and publishing frequent blog posts. The results were fantastic. We ranked on search engine result pages (SERPs) for primary keywords and started attracting new users organically (5-9 users per day). Calday's Organic Growth Thanks to good SEO, we crossed the 500 users milestone last year. Seeing the results, we put more effort into SEO and content. We also changed our domain from "Calday.app" tld to "Calday.com" to further strengthen our brand across the web. Our website is getting more than 10 unique visitors per day from Google as of today. Calday's last 3 months growth
We are proud of our achievements so far, but there's always room for improvement. If I were to restart today, I would prefer to hire an expert marketing team from the start. The good thing was that we realized the importance of SEO and quality content very early on. Promoting Calday on Reddit and LinkedIn was a good tactic that got us early clicks. That's why we plan to revisit our social media advertising strategy following the launch of our mobile application, as we believe the app will be more suited to this type of advertising. Similarly, PPC Ads helped us get going. However, one cannot rely on it, especially if you are short on marketing budget. The most valuable aspect of the campaign was the user feedback we received. This feedback has been instrumental in refining our product, allowing us to enhance each new release and steadily increase our conversion of users into paying customers.
Custom Built Stack 232 using SEO See All Ideas Leveraging SEO Zoom Other 178 using LinkedIn Social Media 12 using Reddit Social Media 1 using Google Analytics Analytics 1,073 using newDocumentReady(function () { $(".database-result-bottom").unbind().click(function() { $(this).find(".database-result-bottom-contents").toggle(); }) // Mobile DB view toggle — switch between the compact row list (default) // and the rich card view, remembered in localStorage. Instant, no reload. // Leading ';' guards against ASI: the preceding statement ends in `})`. ;(function () { var group = document.querySelector('.db-view-toggle-group'); if (!group) return; var rowsPanel = document.getElementById('db-view-rows'); var cardsPanel = document.getElementById('db-view-cards'); function applyView(view) { var isCards = view === 'cards'; if (cardsPanel) cardsPanel.classList.toggle('hidden', !isCards); if (rowsPanel) rowsPanel.classList.toggle('hidden', isCards); group.querySelectorAll('.db-view-toggle').forEach(function (b) { b.classList.toggle('db-view-toggle-active', b.dataset.view === view); }); } var saved = 'rows'; try { saved = window.localStorage.getItem('dbMobileView') || 'rows'; } catch (e) {} applyView(saved === 'cards' ? 'cards' : 'rows'); group.querySelectorAll('.db-view-toggle').forEach(function (b) { b.addEventListener('click', function () { var v = b.dataset.view; try { window.localStorage.setItem('dbMobileView', v); } catch (e) {} applyView(v); }); }); })(); // Search UX: debounced auto-submit + global "/" focus shortcut. // Server-side pg_search is GIN-indexed and returns in <50ms; combined // with the page render, total feedback after the debounce window is // ~500ms — close enough to "live" without an XHR/Turbo refactor. var searchInput = document.getElementById('search'); if (searchInput) { var debounceTimer = null; var lastSubmittedValue = searchInput.value; searchInput.addEventListener('input', function () { clearTimeout(debounceTimer); debounceTimer = setTimeout(function () { // Don't submit if the value hasn't changed (e.g. arrow keys). if (searchInput.value === lastSubmittedValue) return; lastSubmittedValue = searchInput.value; searchInput.form && searchInput.form.submit(); }, 350); }); // Enter still submits immediately — short-circuits the debounce. searchInput.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); clearTimeout(debounceTimer); searchInput.form && searchInput.form.submit(); } }); } // Global "/" shortcut to focus search — GitHub/Linear pattern. document.addEventListener('keydown', function (e) { if (e.key !== '/' || e.metaKey || e.ctrlKey || e.altKey) return; var t = e.target; if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return; var s = document.getElementById('search'); if (s) { e.preventDefault(); s.focus(); s.select(); } }); function prettySliderValue(func, value) { var n = Math.round(Number(value)); if (func == "money") { if (n >= 1000000000) return "$" + (n / 1000000000).toFixed(n % 1000000000 === 0 ? 0 : 1) + "B"; if (n >= 1000000) return "$" + (n / 1000000).toFixed(n % 1000000 === 0 ? 0 : 1) + "M"; if (n >= 1000) return "$" + (n / 1000).toFixed(n % 1000 === 0 ? 0 : 1) + "K"; return "$" + numberWithCommas(n); } else if (func == "daystomonths") { return (n / 30) + " months"; } else if (func == "hours") { return n + " hours"; } else { return n; } } function getInitialSliderValues(el) { var initial = el.dataset.initial; var defMin = Number(el.dataset.defaultmin); var defMax = Number(el.dataset.defaultmax); if (initial) { var parts = initial.split("|"); var a = parts[0] === "" ? defMin : Number(parts[0]); var b = parts[1] === "" ? defMax : Number(parts[1]); return [a, b]; } return [defMin, defMax]; } function createTwoProngedSlider(id) { var el = document.querySelector(id); if (!el || el.noUiSlider) return; var min = Number(el.dataset.min); var max = Number(el.dataset.max); var step = Number(el.dataset.step); var cleanfunc = el.dataset.cleanfunc; var values = getInitialSliderValues(el); var container = el.closest(".range-filter") || el.parentElement; var input = container.querySelector('input[type="hidden"]'); var label = container.querySelector(".preview-label"); noUiSlider.create(el, { start: values, connect: true, step: step, range: { min: min, max: max }, behaviour: "tap-drag" }); el.noUiSlider.on("update", function(vals) { var lo = Math.round(Number(vals[0])); var hi = Math.round(Number(vals[1])); if (label) label.textContent = prettySliderValue(cleanfunc, lo) + " – " + prettySliderValue(cleanfunc, hi); if (input) input.value = lo + "|" + hi; }); el.noUiSlider.on("change", function() { var form = el.closest("form"); if (form) form.submit(); }); } createTwoProngedSlider("#revenue-slider"); createTwoProngedSlider("#starting-costs-slider"); createTwoProngedSlider("#time-to-revenue-slider"); createTwoProngedSlider("#min-hours-per-week"); createTwoProngedSlider("#market-size"); createTwoProngedSlider("#startup-costs"); createTwoProngedSlider("#case-studies"); $(".mobile-filters-applied-button").click(function() { $(".database-filters").addClass("inView") }) $(".close-mobile-filters").click(function() { $(".database-filters").removeClass("inView") }) // Explore "More filters" — reveal/hide the secondary filter pills. var moreFiltersToggle = document.getElementById("explore-more-filters-toggle"); if (moreFiltersToggle) { moreFiltersToggle.addEventListener("click", function () { var panel = document.querySelector(".explore-secondary-filters"); if (!panel) return; var nowHidden = panel.classList.toggle("hidden"); moreFiltersToggle.setAttribute("aria-expanded", String(!nowHidden)); }); } // Infinite scroll lives in its OWN init block below (initSSInfiniteScroll), // deliberately decoupled from this newDocumentReady callback. Anything that // throws above (a slider lib that didn't load from CDN, a jQuery plugin, // etc.) would otherwise abort the rest of THIS callback and silently kill // infinite scroll — the "only works on a hard reload" bug. Keeping it // separate means nothing here can take it down. }); // ---- Infinite scroll (pro/admin/free-access; table + tile views) ---- // Driven off Kaminari's next-page URL, which the partial renders into // #infinite-scroll-sentinel only when the visitor is eligible. We fetch the // next full page, lift out the children of its .database-append-target // container (<tbody> rows for the table view, tiles for the grid views), // append them, then chain to that page's own sentinel URL. DOMParser does // NOT execute the fetched page's scripts, so only content nodes are added — // sliders/search/PostHog never re-run. The sentinel is absent for non-pro // visitors, so this whole block no-ops for them and their paywall is intact. // // Idempotent: guarded by sentinel.dataset.ssInfiniteInit so re-running on // pageshow (bfcache/back-forward restore, where DOMContentLoaded never fires) // can't double-observe an already-wired sentinel. function initSSInfiniteScroll() { var sentinel = document.getElementById('infinite-scroll-sentinel'); if (!sentinel || !sentinel.dataset.nextUrl) return; if (sentinel.dataset.ssInfiniteInit === '1') return; sentinel.dataset.ssInfiniteInit = '1'; // The "database" view renders TWO append targets — the mobile card list // and the desktop table tbody (one is display:none per viewport). Append // to both so infinite scroll works no matter which is visible. Tile views // have a single target. containers[0] drives the loaded-count readout. var containers = Array.prototype.slice.call(document.querySelectorAll('.database-append-target')); if (!containers.length) { delete sentinel.dataset.ssInfiniteInit; return; } var container = containers[0]; // Infinite scroll is live — hide the prev/next fallback for JS clients. var paginate = document.querySelector('.paginate'); if (paginate) paginate.style.display = 'none'; var numResults = document.querySelector('.num-results'); var loadingEl = sentinel.querySelector('.infinite-scroll-loading'); var nextUrl = sentinel.dataset.nextUrl; var loading = false; // Item nodes only — tile partials (e.g. _video) emit a sibling <style> // per render; those are global CSS we already have, so skip them when // appending and counting. function itemNodes(root) { return Array.prototype.filter.call(root.children, function (n) { return n.tagName !== 'STYLE' && n.tagName !== 'SCRIPT'; }); } function updateCount() { if (!numResults) return; var count = itemNodes(container).length; // Keep the "of N …" suffix; just bump the leading loaded-count. numResults.textContent = numResults.textContent.replace(/Showing\s+[\d,]+/, 'Showing ' + count.toLocaleString()); } function loadMore() { if (loading || !nextUrl) return; loading = true; if (loadingEl) loadingEl.style.display = ''; fetch(nextUrl, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(function (r) { return r.text(); }) .then(function (html) { var doc = new DOMParser().parseFromString(html, 'text/html'); var srcs = doc.querySelectorAll('.database-append-target'); Array.prototype.forEach.call(srcs, function (src, i) { var dest = containers[i]; if (!dest) return; itemNodes(src).forEach(function (node) { dest.appendChild(document.importNode(node, true)); }); }); var ns = doc.getElementById('infinite-scroll-sentinel'); nextUrl = (ns && ns.dataset.nextUrl) || null; sentinel.dataset.nextUrl = nextUrl || ''; updateCount(); if (!nextUrl) { observer.disconnect(); sentinel.style.display = 'none'; } }) .catch(function () { /* leave nextUrl intact so a later scroll retries */ }) .finally(function () { loading = false; if (loadingEl) loadingEl.style.display = 'none'; }); } var observer = new IntersectionObserver(function (entries) { entries.forEach(function (e) { if (e.isIntersecting) loadMore(); }); }, { rootMargin: '600px 0px' }); observer.observe(sentinel); } newDocumentReady(initSSInfiniteScroll); // bfcache/back-forward restore doesn't fire DOMContentLoaded; pageshow does. // Idempotency guard above makes the re-call a no-op when already wired. window.addEventListener('pageshow', initSSInfiniteScroll); .blur-row { filter: blur(4px); pointer-events: none; user-select: none; } /* Mobile view toggle (rows ↔ cards) */ .db-view-toggle { display: inline-flex; align-items: center; gap: 5px; padding: 5px 11px; border: 0; border-radius: 6px; background: transparent; color: #64748b; font-size: 12px; font-weight: 600; cursor: pointer; transition: background-color 0.12s, color 0.12s; } .db-view-toggle-active { background: #0f172a; color: #ffffff; } .database-video-grid { display: grid; grid-template-columns: 1fr; gap: 18px; } @media (min-width: 768px) { .database-video-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } /* Airbnb-style range slider (uses noUiSlider) */ .range-filter { display: inline-flex; flex-direction: column; justify-content: center; gap: 4px; padding: 6px 16px 10px; border: 1px solid rgb(226 232 240); background: #fff; border-radius: 9999px; min-width: 220px; margin-bottom: 0 !important; } .range-filter .range-filter-header { display: flex; align-items: baseline; gap: 6px; line-height: 1; } .range-filter .range-filter-header label { font-size: 0.875rem; color: rgb(100 116 139); font-weight: 500; margin: 0 !important; line-height: 1.2; flex-shrink: 0; } .range-filter .range-filter-header label::after { content: "|"; margin-left: 6px; color: rgb(203 213 225); font-weight: 400; } .range-filter .preview-label { font-size: 0.875rem; color: rgb(15 23 42); font-weight: 600; line-height: 1.2; margin: 0 !important; white-space: nowrap; } .range-filter .filter-slider.noUi-target { height: 3px; background: rgb(226 232 240); border: 0; box-shadow: none; border-radius: 9999px; margin: 3px 6px 0; } .range-filter.is-active { border-color: rgb(15 23 42); background: rgb(241 245 249); } .range-filter.is-active .range-filter-header label { color: rgb(15 23 42); } .range-filter .filter-slider .noUi-connect { background: rgb(15 23 42); } .range-filter .filter-slider .noUi-handle { height: 20px; width: 20px; top: -8px; right: -10px; border-radius: 9999px; background: #fff; border: 1px solid rgb(203 213 225); box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12), 0 2px 4px rgba(15, 23, 42, 0.08); cursor: grab; transition: transform 0.1s ease, box-shadow 0.1s ease, border-color 0.1s ease; outline: none; } .range-filter .filter-slider .noUi-handle::before, .range-filter .filter-slider .noUi-handle::after { display: none; } .range-filter .filter-slider .noUi-handle:hover { border-color: rgb(15 23 42); transform: scale(1.05); } .range-filter .filter-slider .noUi-handle.noUi-active, .range-filter .filter-slider .noUi-handle:focus-visible { cursor: grabbing; box-shadow: 0 0 0 6px rgba(15, 23, 42, 0.08), 0 2px 6px rgba(15, 23, 42, 0.18); border-color: rgb(15 23 42); } .range-filter .filter-slider.noUi-horizontal { height: 4px; } .range-filter input[type="hidden"] { display: none; }
IDEA REVENUE Ai-powered sms marketing ... · txtcartapp.com AI-Powered SMS Marketing for Shopify Stores $85K /mo Catholic prayer and medit... · hallow.com "Catholic prayer and meditation app fostering faith growth." — Automated headless browse... · browserless.io Web automation SaaS for developers. $108K /mo No-code website builder · dorik.com 'Website builder for non-coders' — Simple payment link gener... · checkoutpage.com Customizable checkout pages that convert $24K /mo Developer hiring marketplace · distro.io Online marketplace for hiring global software developers. $283K /mo EG Ai-powered linkedin post ... · easygen.io AI tool for generating LinkedIn content. — newDocumentReady(function() { // Save-bookmark hover — delegated on document (guarded once) so rows // appended by infinite scroll get the behavior too. mouseover/mouseout // bubble (mouseenter/leave don't); closest('tr') scopes to the hovered row, // and the relatedTarget check suppresses flicker when moving cell-to-cell // within the same row. if (!window.__saveBookmarkHoverInit) { window.__saveBookmarkHoverInit = true; document.addEventListener('mouseover', function(e) { var row = e.target.closest('tr'); if (!row) return; var saveBookmark = row.querySelector('.save-bookmark'); if (saveBookmark) saveBookmark.classList.remove('invisible'); }); document.addEventListener('mouseout', function(e) { var row = e.target.closest('tr'); if (!row) return; if (e.relatedTarget && row.contains(e.relatedTarget)) return; var saveBookmark = row.querySelector('.save-bookmark'); if (saveBookmark) saveBookmark.classList.add('invisible'); }); } // ---- Tag-pill hover tooltip (LLM-generated context for each Tagging) ---- if (!window.__tagTooltipInit) { window.__tagTooltipInit = true; const tip = document.createElement('div'); tip.className = 'tag-tooltip-popover'; tip.style.cssText = [ 'position:fixed','z-index:9999','max-width:340px','display:none', 'background:#0f172a','color:#fff','font-size:12px','line-height:1.4', 'padding:8px 10px','border-radius:6px','box-shadow:0 8px 24px rgba(0,0,0,.18)', 'pointer-events:none','white-space:normal' ].join(';'); document.body.appendChild(tip); const showTip = function(e) { const target = e.currentTarget; const text = target.getAttribute('data-tag-tooltip'); if (!text) return; tip.textContent = text; tip.style.display = 'block'; const r = target.getBoundingClientRect(); const tr = tip.getBoundingClientRect(); let top = r.bottom + 8; if (top + tr.height > window.innerHeight - 8) top = r.top - 8 - tr.height; let left = r.left; if (left + tr.width > window.innerWidth - 8) left = window.innerWidth - tr.width - 8; if (left < 8) left = 8; tip.style.top = Math.round(top) + 'px'; tip.style.left = Math.round(left) + 'px'; }; const hideTip = function() { tip.style.display = 'none'; }; document.addEventListener('mouseover', function(e) { const el = e.target.closest('[data-tag-tooltip]'); if (el) showTip({ currentTarget: el }); }); document.addEventListener('mouseout', function(e) { const el = e.target.closest('[data-tag-tooltip]'); if (el) hideTip(); }); } });
Who is the owner of Calday? When did Pavlo Grinevich start Calday? How much money has Pavlo Grinevich made from Calday? {"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"Who is the owner of Calday?","acceptedAnswer":{"@type":"Answer","text":"Pavlo Grinevich is the founder of Calday."}},{"@type":"Question","name":"When did Pavlo Grinevich start Calday?","acceptedAnswer":{"@type":"Answer","text":"2023"}},{"@type":"Question","name":"How much money has Pavlo Grinevich made from Calday?","acceptedAnswer":{"@type":"Answer","text":"Pavlo Grinevich started the business in 2023, and currently makes an average of $68.4K/year."}}]}