<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Tales of a web developer]]></title><description><![CDATA[I write about development, product building, and everything related to tech in general. I write once per week, either here or in my dreams.]]></description><link>https://cnicodeme.com</link><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 21:07:36 GMT</lastBuildDate><atom:link href="https://cnicodeme.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA["The things I am good at"]]></title><description><![CDATA[Imagine, you just finished your day job, you arrive at home and you have two hours, just for you. Your kids - if you have any - are at a relative and your partner won’t be home for the next two hours with that amazing take-out you love. No need to cl...]]></description><link>https://cnicodeme.com/the-things-i-am-good-at</link><guid isPermaLink="true">https://cnicodeme.com/the-things-i-am-good-at</guid><category><![CDATA[llm]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Tue, 10 Feb 2026 13:35:17 GMT</pubDate><content:encoded><![CDATA[<p>Imagine, you just finished your day job, you arrive at home and you have two hours, just for you. Your kids - if you have any - are at a relative and your partner won’t be home for the next two hours with that amazing take-out you love. No need to clean the house, prepare the meal or do anything:</p>
<p>A time just for you.</p>
<p>So you fire up your favorite game, install yourself comfortably, bring some beverage and kick start that game you had in mind all day.</p>
<p>With just a twist.</p>
<p>Once ready, you ask GameGPT to take control, and you let it play for you. You cheer at every wins, kills, progress it makes, because in the end its YOUR game, the progress is for your account, so that counts, right ?</p>
<p>But you feel something odd. It makes total sense to let GameGPT take control. After all, it’s faster than you, more precise, and can even kill the enemy while you go search another bottle of your favorite drink.</p>
<p>But the fun isn’t really there anymore, so you decide to take the control back. You define some rules ;</p>
<p><em>“I’ll only let GameGPT tackle the hard ones, but I’ll have fun on the rest, the things I’m good at!”</em></p>
<p>So you take your pad, and start playing. The game is fun again. You make progress. Granted, you are slower, but this time you know what you do and where you are going.</p>
<p>At some point, you hit a boss. You try a few times but can’t beat it. Despite your best efforts, it seems you won’t be able to succeed.<br />No worries, that’s the deal, you fire up GameGPT and it destroy it in no time.</p>
<p>Level up.</p>
<p>You take back the rein, make some progress, hit some tricky adversaries, but you manage to get to the next boss. This time, you don’t bother, you fire up GameGPT directly, kill the boss.</p>
<p>Level up.</p>
<p>Okay, this level is a bit harder, but that makes sense, as we progress, the game follow our experience, so you go through. But damn, those small enemies are hard. You don’t want to tell it, but we all know you just fired up GameGPT on some of these, at least that way you’re making progress. You hate being stuck.</p>
<p>Boss level, GameGPT, dead in no time.</p>
<p>Level up.</p>
<p>Your turn. You move, face an enemy, but wait! What’s the button to fire that special attack that is so efficient ? GameGPT will know, you’ll let it handle this. Well, the enemies too. All the enemies at that point because now, everything seems hard. And hard is frustrating. And <em>you don’t like being frustrated anymore.</em></p>
<p>And as you watch GameGPT doing all the grunt work, you think back of your original rule:</p>
<p><em>“I’ll only let GameGPT tackle the hard ones, but I’ll have fun on the rest, the things I’m good at!”</em></p>
<p>…</p>
<p><em>“The things I’m good at.”</em></p>
<p>…</p>
<p><em>“What am I good at anymore ?”</em></p>
]]></content:encoded></item><item><title><![CDATA[I'll build this later]]></title><description><![CDATA[Your roadmap is a cat-and-mouse game between new ideas/features/bugs that are added to it versus what you and your team can tackle. Ideally, it should go down, but it’s often the contrary that happens.
And then, you get a customer asking you for some...]]></description><link>https://cnicodeme.com/ill-build-this-later</link><guid isPermaLink="true">https://cnicodeme.com/ill-build-this-later</guid><category><![CDATA[fernand]]></category><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Sat, 17 Jan 2026 10:23:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/um1zVjVCtEY/upload/91f1d35ecb929c9bcf2953aa27a7ab89.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Your roadmap is a cat-and-mouse game between new ideas/features/bugs that are added to it versus what you and your team can tackle. Ideally, it should go down, but it’s often the contrary that happens.</p>
<p>And then, you get a customer asking you for something that could be implemented on your service, but it’s faster to do it manually, respond to the customer and close the ticket.</p>
<p>Often, this is misleading because this one, isolated, customer indeed was faster to process manually than to build the feature directly, but what you tend to forget, is that a few others will ask the same thing.</p>
<p>And when you stack all these requests, the time spent is longer than it would have taken to just implement that feature.</p>
<p>In general in my life, I try to apply the principle that if it takes less than 5 minutes to do, I’ll do it immediately.<br />This is not really helpful for the present “you”, but your future “you” will thank you.</p>
<p>You put your tools on the kitchen table, thinking “I’ll move them to the garage when I’ll have the time”, and things start to pile up. When you do it now, you have less clutter, and one thing less on your mind. Granted, you did one more trip to the garage, but hey, you did more steps today :)</p>
<p>That’s the same principle we are applying at <a target="_blank" href="https://getfernand.com">Fernand</a>; If it takes less than 30 minutes to implement, we’ll do it right away, because we believe that if one customer reaches out about a request, 10 others had the same request but never bothered to ask.</p>
<p>We recently had this scenario. Until now, it wasn’t possible to change who was the owner of an organization. To change that, you had to reach out to us and we would manually change the ownership in the database.</p>
<p>Clearly, this is a typical case of “it just takes less than a minute to update the database”, so it didn’t make sense to jump on developing the feature right away. On top of that, we have a massive roadmap and this request is not in the top priority.</p>
<p>But this customer wasn’t the first, and certainly not the last. On top of that, I discovered a sneaky problem: the one requesting to change ownership was not the owner, but someone else in the team. What if it was just a simple, bad intentioned employee ? They could be a new hire that tried to take over the account.</p>
<p>So I worked on that feature, and less than half an hour later, it was possible - for the owner only - to give ownership to another team member.</p>
<p>I told that user about the new feature and let them change ownership directly from their owner.</p>
<p>That’s one request we won’t have to handle anymore, giving us more time to work on important things.</p>
]]></content:encoded></item><item><title><![CDATA[Implementing Fernand backend with Sanic]]></title><description><![CDATA[Now that Fernand is out, it's time for me to reflect back on how I spent the last few years (yes, years), working on this crazy project.
Just to clarify things, Fernand is our latest project, a Help Desk SaaS with a focus on speed, efficiency, and ca...]]></description><link>https://cnicodeme.com/implementing-fernand-backend-with-sanic</link><guid isPermaLink="true">https://cnicodeme.com/implementing-fernand-backend-with-sanic</guid><category><![CDATA[Python 3]]></category><category><![CDATA[Sanic]]></category><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Wed, 08 Oct 2025 09:11:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/g5_rxRjvKmg/upload/9db6340ea0567bd1967719dec1e1a965.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Now that <a target="_blank" href="https://getfernand.com">Fernand</a> is out, it's time for me to reflect back on how I spent the last few years (yes, years), working on this crazy project.</p>
<p>Just to clarify things, <a target="_blank" href="https://getfernand.com">Fernand</a> is our latest project, a Help Desk SaaS with a focus on speed, efficiency, and calm. We were just two working on this, Antoine and me. Antoine is the designer and marketing specialist behind the voice of Fernand, and I'm the technical one.</p>
<p>This article is about the evolution of the technical side.</p>
<p>Fernand first started being built on Flask. I've been building all my recent projects using Flask and I've a pretty good grasp of how it works. <a target="_blank" href="https://improvmx.com">ImprovMX</a>, our other service, is running on Flask with a few modified internals specific to our needs. So it made sense to build Fernand using Flask too.</p>
<p>But while we were heads down on building it, we had a few issues on ImprovMX that was caused because some requests would take too long to complete. The issue is that when Flask accepts a request, it will block a thread for this request until it is completed. if that requests fetch a lot of data from the database and takes time, the thread won't be free for other users while waiting for the database results.</p>
<p>At that time, I was curiously checking the landscape to see if there weren't anything new I could use on Fernand rather than staying with the old (but reliable) Flask. I had seen FastAPI, Sanic, Starlette and a few others, and tried FastAPI and Sanic. What was interesting me was the A in ASGI: <strong>Asynchronous</strong> Server Gateway Interface. I had a clear vision that this would become handy, moreover because recent Python versions introduced the <code>async</code> keyword. It made sense.</p>
<p>After the downtime we had at ImprovMX, and once I discovered it was because some endpoints were slow (yes, I fixed them to improve their response time), it made sense for me to move over an ASGI framework.</p>
<p>Sanic was appealing to me. Firstly because it was tested as one of the fastest Python framework, but also because the way the codebase, and how the flow was done, was more how I think.</p>
<p>So here I was, migrating the whole Flask codebase to Sanic.</p>
<p>I'm not a fan of adding decorators on top of decorators, so having a :</p>
<pre><code class="lang-plaintext">@ratelimit(5)
@authenticated
@db
@app.get('/')
async def show(request):
    limit = request.args.get('limit', 20)
    return sanic.response.json(await MyModel.get_latest(limit))
</code></pre>
<p>Was absolutely not appealing to me.</p>
<p>So I rewrote how the routes were handled, and implemented a few ideas from FastAPI along the way. Now, you could do this :</p>
<pre><code class="lang-plaintext">@app.get('/', ratelimit=5, auth=True)
async def show(user, limit:int = 20):
    return await MyModel.get_latest(limit)
</code></pre>
<p>Way more cooler isn't it?</p>
<p>A few things to note from this:</p>
<ol>
<li><p>There are no more "lots of decorators", all the properties are defined in the route ("@app.get"), which makes more sense - at least for me</p>
</li>
<li><p>The route doesn't impose the <code>request</code>parameter anymore, and you can have the user parameter from the auth parameter, but it's optional (if you don't ask it, it won't be added in the function)</p>
</li>
<li><p>The request parameter is added if asked, and cast to the proper type if type hint is provided</p>
</li>
<li><p>The response is adjusted based on what is returned. I went a bit beyond what can be seen here, because it's possible to return a Model object, and it will be serialized based on more advanced code.</p>
</li>
</ol>
<p>These two decisions, from using Sanic as the main framework, and rewriting the router to handle more parameter is the best decision we could have for a clear code base. I’m really happy with how things turned out :)</p>
]]></content:encoded></item><item><title><![CDATA[Why we're building another Customer support tool]]></title><description><![CDATA[We've been building products with Antoine for more than 15 years now, and we had our fair share of successes (Voilanorbert, Transferslot, PDFShift, and ImprovMX to name a few). Around three years ago, we were heads deep on growing ImprovMX and were f...]]></description><link>https://cnicodeme.com/why-were-building-another-customer-support-tool</link><guid isPermaLink="true">https://cnicodeme.com/why-were-building-another-customer-support-tool</guid><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Tue, 17 Oct 2023 07:07:49 GMT</pubDate><content:encoded><![CDATA[<p>We've been building products with Antoine for more than 15 years now, and we had our fair share of successes (<a target="_blank" href="https://voilanorbert.com">Voilanorbert</a>, <a target="_blank" href="https://transferslot.com">Transferslot</a>, <a target="_blank" href="https://pdfshift.io">PDFShift</a>, and <a target="_blank" href="https://improvmx.com">ImprovMX</a> to name a few). Around three years ago, we were heads deep on growing ImprovMX and were facing the harsh reality of email forwarding when we had a talk about our ideal product.</p>
<p>We spent the evening discussing what "ideal" would mean for us, and we came up with the following key elements we believed matched it:</p>
<ol>
<li><p>A consequent market size</p>
</li>
<li><p>A specific, unique selling point</p>
</li>
<li><p>A non-critical service</p>
</li>
</ol>
<h2 id="heading-defining-success">Defining success</h2>
<p>The first thing we need to talk about is defining what success means.</p>
<p>For some, it's getting funded with millions in the bank, a team of 100, 1000 employees, a big company, and a shot at being a unicorn.</p>
<p>For us, it's the opposite. We aim to get enough monthly revenue to not stress about tomorrow. We aren't looking to have a multi-million dollar company.</p>
<p>That's what we call success: enough MRR to have a decent salary and the liberty to do what we want, how we want, with our company.</p>
<p>With that in mind, here's the key element we defined to build our ideal service:</p>
<h2 id="heading-a-consequent-market-size">A consequent market size</h2>
<p>The Customer support market is 44 billion USD Dollars in 2023. We never had the big ambition to become the leader in a market, but we thought that if we could attract 0.1% of that market, we would be in a really great position to do what we love with no economic issues in the foreseeable future.</p>
<p>This isn't our first stunt in trying to attack a big market. Around 2012, we started working on 2Lead.in, a CRM tool that was using new javascript features at the time (Backbone.js! Old time!!). We built something really nice, well-designed, and ... dare I say ... complete, but we never had the courage to officially release it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697525406824/7866314e-66c7-4d07-9f6e-89f2de6b3fa2.jpeg" alt class="image--center mx-auto" /></p>
<p>It turns out that at some point, we had the idea to go on the Wikipedia page to see the list of competitors we were up against, and when we discovered there were hundreds of alternatives, this discouraged us, and we closed the product.</p>
<p>Looking back, we both regret not publishing the product. Granted, it wasn't certainly complete, but it was definitely nice and well done, and could have grasped some market share.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697525601441/7305c3a1-8d41-45a0-a576-23d4770670ef.jpeg" alt class="image--center mx-auto" /></p>
<p>If we had released it, we would have kept on working on it, and iteration by iteration, we would have made the product better, and grew up in that massive list of hundreds of competitors, grabbed some market share. We're now both confident to say that we would have managed to have a product generating a nice income.</p>
<p>But our lack of experience made us scared, and we shut it down for good. It stings a bit to talk about it.</p>
<p>It is with <a target="_blank" href="https://voilanorbert.com">VoilaNorbert</a> that we realized that the principal point of having a successful product is to first release it. You'll never make a dime if you never publish anything.</p>
<p>But that's not all. If you decide to go against a big market, you'll need a unique approach, something to separate you from the others, something to put you aside from the noise.</p>
<h2 id="heading-a-specific-unique-selling-point">A specific, unique selling point</h2>
<p>You can't compete with hundreds of other services in the same market if you don't stand out. Your gut might tell you otherwise when you start working on that new, shiny product you have in mind right now, but take a step back and think about it.</p>
<p>Would you prefer to use a well-established product or a newcomer for the same service?</p>
<p>If your counter-argument to that is "I'll lower my prices", you are just creating a race to the bottom. In a market where you might grasp some customers at first, you'll have trouble raising your price because your customers will eventually move to that well-established competitor you initially went against.</p>
<p>Another way is to offer something different, something new that will make you stand out. You don't need 100% of the market you are going into. You just need enough for you to live.</p>
<p>That's what we decided for Fernand with Antoine. Instead of going with a generic customer support service, we decided to build a customer support service specifically for SaaS companies, that is fast and focuses on giving a calm experience. We're aware we are limiting our reach (SaaS companies that resonate with the "calm" experience), but we are also aware that the market is so big that this will be enough for what we want to achieve (again, not an unicorn)</p>
<p>This leads me to the last point of our ideal product:</p>
<h2 id="heading-a-non-critical-service">A non-critical service</h2>
<p>There is a lot of market you can take a shot at. We tried that with <a target="_blank" href="https://voilanorbert.com">VoilaNorbert</a> (an email searching tool), <a target="_blank" href="https://transferslot.com">Transferslot</a> (a marketplace to sell and buy SaaS products), <a target="_blank" href="https://pdfshift.io">PDFShift</a> (API to convert HTML documents to PDF), and <a target="_blank" href="https://improvmx.com">ImprovMX</a> (an email forwarding tool). All of them have their specificities, but some of them are extremely important to the users.</p>
<p>Specifically, ImprovMX is so important that we can't allow any downtime or outage to occur, and when they do, it's always hard for us to handle any outages while knowing we might be losing our user's email.</p>
<p>This puts constant stress on us, and we spend a fair amount of time to ensure everything is working fine.</p>
<p>If I recall correctly, the discussion we had about the ideal product started exactly after an outage at ImprovMX. We were exhausted from fighting the problem, and once it was solved, we were almost ready to sell it. As of today, I believe that the email market is a world where you <em>do</em> need investors' money, a consequent team to ensure 24/7 engineers are ready to jump on any issues, and great teams of developers and sysops to ensure the best reliability possible.</p>
<p>In contrast, a Customer support service is less critical. Of course, it still requires to work well anytime, but compared to losing emails, if the app is down for an hour, it won't be <em>that</em> problematic.</p>
<h2 id="heading-all-products-have-an-important-critical-aspect">All products have an important, critical aspect</h2>
<p>Plot twist, all the service have their critical elements.</p>
<p>For <a target="_blank" href="https://getfernand.com">Fernand</a>, it's receiving emails. We can't afford to lose any incoming emails and tell our customers that the very important email one of their customers sent was never received by us. That is not possible. At all.</p>
<p>But we are clearly aware of this, and we've implemented a ton of foolproof and redundant ways to ensure this doesn't fail. Our code works with both Amazon SES and ImprovMX to receive emails, in case it goes down. Every email is saved before being processed in case our server fails for some reason (like the database). Finally, we have checks in place to ensure any emails saved are properly processed in case our tasks system fails to process a specific email for some reason.</p>
<p>We uncovered what is our critical point, and we've made 200% sure that we can't miss an email.</p>
<p>When we settled to do a customer support service with Antoine, our initial assumption was that having an app wasn't as crucial as forwarding email. The worst case scenario would be that the app is down for some time, so the customer agents can't do their job, but once it's back, they haven't lost anything.</p>
<p>But in retrospect, that is not the case. If we're down, we still need to ensure we can receive emails.</p>
<p>And you, what is the critical point of your service?</p>
<p>(If you answer "nothing," I recommend you to check again. What about the database? The third-party service you rely on?)</p>
<h2 id="heading-outro">Outro</h2>
<p>After discussing the whole evening with Antoine about what kind of product we could build that would be in a big market, that we could have a unique approach to it, and where it wasn't absolutely critical, we realized that the customer support landscape was a great market to try, and this is how Fernand was born.</p>
<hr />
<p>I hope this was interesting to read. If you have any questions, feel free to ask in the comment; I'll happily answer them!</p>
]]></content:encoded></item><item><title><![CDATA[The right audience is mandatory]]></title><description><![CDATA[I often read that ProductHunt is necessary to reach a broader audience, and having your product not reach the top 5 is considered a failure.
But this doesn't apply to everyone. You need to consider which audience your product is targeting and focus t...]]></description><link>https://cnicodeme.com/the-right-audience-is-mandatory</link><guid isPermaLink="true">https://cnicodeme.com/the-right-audience-is-mandatory</guid><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Mon, 03 Jul 2023 15:31:52 GMT</pubDate><content:encoded><![CDATA[<p>I often read that ProductHunt is necessary to reach a broader audience, and having your product not reach the top 5 is considered a failure.</p>
<p>But this doesn't apply to everyone. You need to consider which audience your product is targeting and focus there.</p>
<p>Granted, if you can reach the top #5 in ProductHunt, you'll get great visibility that is always welcome, but that won't necessarily translate to quality users or even paid users.</p>
<p>(If you did a successful launch but got no paying customers from PH, raise your hand).</p>
<p>Let me share a history of why targeting the right audience is important.</p>
<p>During the weekend, @staticmaker1 <a target="_blank" href="https://twitter.com/staticmaker1/status/1674607939724918784">posted a tweet</a> (thank you so much!) talking about <a target="_blank" href="https://pdfshift.io">PDFShift</a>. It got 320k+ views (as of writing) and resulted in an impressive spike of visitors:</p>
<p><img src="https://i.imgur.com/c4yCElS.png" alt="Analytics from Plausible.io" /></p>
<p>This was a 35x from my usual number of daily visitors.</p>
<p>You would believe that going from ~215 daily visitors to ~7,500 would also generate a spike in the number of new users, but it turns out that the audience wasn't the one interested in converting HTMLs to PDF, so the number of new users didn't follow that 35x increase:</p>
<p><img src="https://i.imgur.com/1XLsaFi.png" alt="Daily number of new users at PDFShift" /></p>
<hr />
<p>Don't get me wrong here. I'm very happy that @staticmaker1 mentioned PDFShift in their tweets. This brought some great visibility for <a target="_blank" href="https://pdfshift.io">PDFShift</a> and planted the name in the mind of many people that might think of PDFShift in the future. I'm really thankful they mentioned PDFShift 🙏</p>
<p>But the result proves that the appropriate audience is a key aspect when getting recognition for your product.</p>
<p>If @TomCruise writes a tweet about your product (I'm mentioning him since he's getting the headlines with his upcoming movie :D), you'll get a TON of visitors, that's for sure, but not so many will convert to actual users. And in these users, most of them will be lost and won't understand what you are offering. They aren't your appropriate target, that's all.</p>
<p>For this reason, ProductHunt isn't always necessary. And failing to finish on the top 5 isn't a fatality for your product if it isn't the right audience.</p>
<p>So, repeat after me: "The right audience is mandatory when sharing your product"</p>
]]></content:encoded></item><item><title><![CDATA[Don't miss an opportunity to convert]]></title><description><![CDATA[There is one service I like to connect to for all my projects, it's Customer.io.
It allows you to send events that trigger a campaign with specific workflows and works wonderfully for many areas, such as onboarding, upgrades, and churns.
But there is...]]></description><link>https://cnicodeme.com/dont-miss-an-opportunity-to-convert</link><guid isPermaLink="true">https://cnicodeme.com/dont-miss-an-opportunity-to-convert</guid><category><![CDATA[convert]]></category><category><![CDATA[customer success]]></category><category><![CDATA[growth]]></category><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Tue, 16 May 2023 20:15:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/nN5L5GXKFz8/upload/0dad602fefed7a91704f27f912df7c16.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There is one service I like to connect to for all my projects, it's <a target="_blank" href="https://customer.io">Customer.io</a>.</p>
<p>It allows you to send events that trigger a campaign with specific workflows and works wonderfully for many areas, such as onboarding, upgrades, and churns.</p>
<p>But there is also another way you can use Customer.io to improve your conversion.</p>
<p>I recently realized that it could happen that a few users on <a target="_blank" href="https://pdfshift.io">PDFShift</a> would try to upgrade to a paid plan, but had issues because their card was refused (for a number of reasons). Up until then, this was left as-is, and if the user wouldn't retry again, I would not know about nor try to learn more about why they finally dropped it.</p>
<p>(When you think about it, that's kind of crazy. You have a user that is interested to pay for a product and tries to upgrade but their bank is refusing the payment, so they drop it and go somewhere else. You definitely need to have actions in place here to catch them before they leave! That seems so obvious when you think about it!!)</p>
<p>So here's what I did. I created a new campaign on Customer.io that would be triggered by an event, and every time a payment would fail on Stripe, I would receive a webhook event.</p>
<p>On my server, upon receiving that event, I would check if the payment is from a new customer (no previous successful payment), in which case I would send the event `first_payment_failure` to Customer.io.</p>
<p>(I have other actions set up when a recurring payment fails)</p>
<p>Then, over at Customer.io, I created a campaign based on that event's trigger, that would wait for a few hours (I choose 2 hours), then it would check if that user hasn't upgraded in between. If they are still a free user, I would send them an email asking them what happened, and how I can help.</p>
<p>Of course, the sender would be my personal email. That way, when they respond, they get into my inbox directly and I'm the one responding, which shows that I'm invested in helping them.</p>
<p>This doesn't bring too much work; it's not often that a user can't upgrade and decide to leave (thankfully) but when this happens, Customer.io has my back and I can still try to win that customer!</p>
<p>More broadly, you need to be aware of any areas where you could be losing a conversion (being a customer, a subscriber or anything else). If you can find a way to track them and then find ways to reach out to them to see how you could help, it could be a great way to show how you are motivated to help them, and it could make a difference. (Of course, never be too pushy or reach out if they haven't shared their email to you voluntarily).</p>
<p>Good luck :)</p>
]]></content:encoded></item><item><title><![CDATA[Iterating]]></title><description><![CDATA[There is a nice thing about having a project in the long run, it's the increasing number of edge cases covered.
When you first release your code, you have a set of checks you've implemented that covers most cases, but often, you miss a few that can c...]]></description><link>https://cnicodeme.com/iterating</link><guid isPermaLink="true">https://cnicodeme.com/iterating</guid><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Tue, 09 May 2023 19:53:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/L0oJ4Dlfyuo/upload/f17f0c1747909d462de9a57f5d2888a1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There is a nice thing about having a project in the long run, it's the increasing number of edge cases covered.</p>
<p>When you first release your code, you have a set of checks you've implemented that covers most cases, but often, you miss a few that can crash your app.</p>
<p>And having your app crash is never a great feeling as a founder when you then need to reach out to your users to explain why your service stopped working. You might feel dumb because that bug was easy to guess, or it should never have happened in the first place, but you forgot one little thing and it came tumbling down.</p>
<p>But instead of feeling down, ashamed or wanting to stop your project, you should consider this an opportunity. An opportunity to improve your service.</p>
<p>Granted, this bug wasn't great, but now, you're sure it won't happen again because you've implemented the needed precautions you initially forgot about. To put this differently:</p>
<p><em>Your product got better!</em></p>
<p>So next time you check your emails and see all the warnings about your server not working, consider this as an opportunity to improve what you offer. The issue you are having is the last time you'll have it, and everything will be better from now on.</p>
]]></content:encoded></item><item><title><![CDATA[Everyone's prentending]]></title><description><![CDATA[I recently finished the fantastic TV series "Halt and catch fire" (highly recommended) and one sequence struck a chord with what we live in nowadays on the www.
Here's that sequence:
https://youtu.be/A6rS17qTwwQ?t=58
I recall when I was working on my...]]></description><link>https://cnicodeme.com/everyones-prentending</link><guid isPermaLink="true">https://cnicodeme.com/everyones-prentending</guid><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Mon, 10 Oct 2022 19:47:24 GMT</pubDate><content:encoded><![CDATA[<p>I recently finished the fantastic TV series "Halt and catch fire" (highly recommended) and one sequence struck a chord with what we live in nowadays on the www.</p>
<p>Here's that sequence:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/A6rS17qTwwQ?t=58">https://youtu.be/A6rS17qTwwQ?t=58</a></div>
<p>I recall when I was working on my first websites. I was more interested in learning how to write and publish websites, and sharing that knowledge along the way. I wasn't pretending to know, I was only sharing things that I had found working, in the hope of helping the next fellow.</p>
<p>But I believe it all changed with the arrival of social networks.</p>
<p>The arrival of social networks came with a critical component included: followers. With it, you could display your (virtual) success to everyone, showing who was the most popular kid in town.</p>
<p>And just like that, we went from a place where anyone could freely write anything, to a place where everyone started to pretend. They started pretending how great they were on their field. They started pretending how they look. They started pretending how rich they are.</p>
<p>What made the web free in the 90s sense described in the video above was lost to those who fights to be the most popular.</p>
]]></content:encoded></item><item><title><![CDATA[Coming up with a solution]]></title><description><![CDATA[It took a while for me to write this post, but I came to a solution a few days after writing my latest post.
Thinking to migrate to Firebase, I started to dig more under the hood of how Firebase was working. I liked the idea to have data retrieved ve...]]></description><link>https://cnicodeme.com/coming-up-with-a-solution</link><guid isPermaLink="true">https://cnicodeme.com/coming-up-with-a-solution</guid><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Mon, 08 Aug 2022 13:13:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/-87JyMb9ZfU/upload/v1662712929015/-ZnuK4Wlp.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It took a while for me to write this post, but I came to a solution a few days after writing my latest post.</p>
<p>Thinking to migrate to Firebase, I started to dig more under the hood of how Firebase was working. I liked the idea to have data retrieved very fast but was curious how it could send a lot of data under 200ms.</p>
<p>Turns out it doesn't. What it does is keeping a connection alive and receiving what the queue has to offer to that current connection. If data comes between two requests, it is queued and sent once the new request is ready.</p>
<p>I tried to implement something similar, and the result was crazy. I could send a "retrieve" request (GET in a RESTful way) that would respond immediately with an ACCEPTED status code, and once the requested data was ready, it would be published on that long-polling request or pending until that request would be open.</p>
<p>Of course, I had to rewrite quite some code, but it reduced the code I had implemented and made me write something that was clearer. Win-win !</p>
<p>After a few days of working on it, I discovered something problematic. You are limited on how many long-polling XHR request you can have in a browser for a given domain. This means that any user wanting to open many tabs will quickly have issues.</p>
<p>That's when I started to dig more into websockets. I was already aware of this while I was researching long-polling, but I had the misconception that it was not as widely available as it is.</p>
<p>It turns out that websocket is ready and working very well on all browsers. Moreover, I'm using Sanic on the backend, which has a very good implementation of websockets. Migrating the long-polling request to websocket was very easy, and the result was impressive.</p>
<p><strong>So, how does it work ?</strong></p>
<p>It's quite simple in the end.</p>
<p>I always try to have my server-side structure following the REST pattern, the best I can. So I did the same, but adding a realtime layer on top of that.</p>
<p>Concretely, there is only one command that requires data ; It's GET. The other commonly used commands (PATCH, PUT, POST, DELETE) are actions that needs validation and acknowledgement, so you can't fire-and-forget these.</p>
<p>I've added a custom options on my GET functions that, when enabled, will respond immediately with an ACCEPTED status code, and then continue to work by executing the requested work (listing the users, for instance).</p>
<p>Instead of returning them to the client, the results, fetched one by one for faster processing, are sent to a queue along with a "request identifier".</p>
<p>On the consumer end of the queue, a websocket event is sent to the user with the request ID and the data available. Once all the data has been sent, a "done" event with the request ID is sent to indicate the client the data has been sent.</p>
<p>The functions handling these GET requests and sending the data to the queue is build in a hybrid way : It will send the resulting data one by one to the queue for the websocket, but only if a specific header is present to behave that way (simply, the request-id set by the browser, which will be able to match the responses received by the websocket). If that header is missing, the data is returned as a standard API response in JSON.</p>
<p>Any POST/PUT/PATCH/DELETE requests are synchronous and always respond once the request work has been done, with the updated information (or removed in case of a DELETE). But in parallel, an event is fired to all the websocket connections to notify of any changes.</p>
<p>That way, when a user updates some data, all the other connected users have their data updated in realtime.</p>
<p>Of course, a security layer is added and only the users belonging to the same organization have these updates. This means that an organization won't have access to data from another organization or be notified from their changes.</p>
<p>This solution is close to what Firebase is offering, except that the logic is done on the server side (filtering, ordering, etc) rather than on the client side. But that's just a decision to lower the amount of data the user will handle.
Every data received while connected are stored in the memory of the browser and the code listens to any changes (update/delete) and updates them appropriately.</p>
<p>When the user closes the tab and re-open it later, the first connections loads the essential data again and will receive any subsequent changes and apply them.</p>
<p>This is done simply because it would be too complicated to track the database state right before the user closed the tab, and send only the list of changes when they come back. The data sent when re-opening the service is not that much and with current Internet bandwidth, it only takes a few ms to fully load.
Moreover, the data loaded is done in the background while the application shows key elements, so when the user access these data, they are already present.</p>
<p><strong>Conclusion</strong></p>
<p>I hope this was interesting. The whole quest of finding an efficient, fast and always-up-to-date interface was quite challenging and made me rethink how we load and access data.</p>
<p>This required me to re-think and re-write how the backend behave, and how the frontend could rely on the browser to speed up the process.</p>
<p>A future iteration of this would be to rely even more on the browser features to store data (IndexedDB for instance) in order to fasten the rendering of the application, but reload everything from the server and update what is needed.</p>
<p>Taking into consideration slow browsers and slow internet connections will be something else to dig into, which gives this project a nice and complex technical thought.</p>
]]></content:encoded></item><item><title><![CDATA[Falling down the rabbit hole]]></title><description><![CDATA[While working on ways to implement a faster and efficient data layer for Fernand (see my last post here), I (re)discovered Firebase and their real-time database called Firestore.
It has a lot of features that any web developer would love, such as Aut...]]></description><link>https://cnicodeme.com/falling-down-the-rabbit-hole</link><guid isPermaLink="true">https://cnicodeme.com/falling-down-the-rabbit-hole</guid><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Fri, 26 Nov 2021 14:06:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/xdai21Y9pek/upload/v1662713037985/-1a6U0Ybs.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While working on ways to implement a faster and efficient data layer for <a target="_blank" href="https://getfernand.com">Fernand</a> (see my <a target="_blank" href="https://write.as/cyril/a-better-approach-to-fetching-the-server-side-data">last post here</a>), I (re)discovered Firebase and their real-time database called <code>Firestore</code>.</p>
<p>It has a lot of features that any web developer would love, such as Authentication (with many protocols if needed, including emails for password loss, account validation, etc), Offline mode, a really fast query model, and real-time synchronized data across all the services.</p>
<p>The whole set resonates well with what we want at Fernand, which includes being fast (data should be loaded in milliseconds) and reliable (it should never fail).</p>
<p>Firebase solve a lot of these issues, and that's what pushed me to heavily consider it. With it, we won't have to worry about the database being down, slow queries, WebSocket failing, etc...</p>
<p>Moreover, the code required to implement is small and easy to get started, which is a great plus too.</p>
<p>But I'm afraid of the issues related to now having access to the database directly. I can work my way around the restrictions now (not building complex queries for instance). But if tomorrow we need to calculate the number of users based on some advanced criteria, we will have to download the database locally, and import it on another datastore (such as MongoDB) in order to run these processes. It feels cumbersome.</p>
<p>Another problem I'm not comfortable with is duplicate content. With a relational database, you can have the guarantee that one element won't be present twice in your database. With Firebase, there are no unique checks done, so you can have two incoming requests having the same data, and, somehow, end up with two "unique" values.</p>
<p>One other drawback is that I will have to rewrite the entire backend system we've implemented to now use the Firebase DB instead of MySQL. We are still small and starting, so it's not the end of the world, but what we have is working really well, and we will have to test it again with the new structure.</p>
<p>I agree that Firebase is incredibly fast to return data - my tests shows an average of 20ms! - But you can be the fastest in the universe, you won't be enough if your user is loading 5000 tickets with a slow 2G connection! You can't compete with that! Not even Firebase.</p>
<p>So here I am, pondering the pros and cons, and being quite stuck on the matter...</p>
<p>See you next episode for my takes on the subject!</p>
<p>Thanks for reading.</p>
]]></content:encoded></item><item><title><![CDATA[A better approach to fetching the server-side data.]]></title><description><![CDATA[(Fernand is the new helpdesk service we are building at the time of the writing. It's not publicly live yet even though we've already spent 18 months on this. The release is soon!).
When I first developed the frontend part of Fernand, I went the naiv...]]></description><link>https://cnicodeme.com/a-better-approach-to-fetching-the-server-side-data</link><guid isPermaLink="true">https://cnicodeme.com/a-better-approach-to-fetching-the-server-side-data</guid><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Tue, 16 Nov 2021 13:41:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/-Vqn2WrfxTQ/upload/v1662713214438/l3AVhwn_u.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>(Fernand is the new helpdesk service we are building at the time of the writing. It's not publicly live yet even though we've already spent 18 months on this. The release is soon!).</p>
<p>When I first developed the frontend part of <a target="_blank" href="https://getfernand.com">Fernand</a>, I went the naive way: The API would load each endpoint when needed, by respecting the RESTFul protocol.</p>
<p>That meant loading all the conversations when you'd hit the "/tickets/" page. That meant waiting for all the conversations and contact details to load.</p>
<p>Granted, I've spent a fair amount of time on improving that endpoint, but it still took about a second to render it properly.</p>
<p>But this wasn't the main issue. The main issue is that by doing so, the content (messages, responses, notes, and events) wasn't loaded, because we couldn't know in advance which one you'd open!</p>
<p>So once the loading was done for the listing, we would wait for the user to make a decision before loading what was needed.</p>
<p>When you would load a conversation thread, it would then fetch the API to load the messages, the contact in details (subscription status, latest payments, etc) and also the list of related conversations from that same user.</p>
<p>That loading could take some time - about two seconds - which would result in a bad User eXperience where you would have to wait when switching between tickets.</p>
<p>Not. Great.</p>
<p>That's why I decided to change the way we would load the data. The idea was that we would constantly load the data we expect the user to view before they make any actions. That way, once they click on a conversation, they have all the details needed in a few milliseconds.</p>
<p>The trick in all of that is to take into consideration the limits of what our users might have: We might reach a memory limit if we load too much, we could slow their bandwidth or worse, use all their data allowance!</p>
<p>Here's my discoveries.</p>
<h2 id="heading-first-approach-load-the-linked-data-in-the-same-request">First approach: Load the linked data in the same request</h2>
<p>My first test was to consider loading all the data related to one conversations in the same request. This forced me to rewrite a bit of the API to return the batch of conversations requested, with extended informations in it (messages, events, notes, contact, related conversations and payments informations)</p>
<p>I implemented a straightforward approach on the API side and I'm pretty sure I could have improved the queries to the database, but I first wanted to see what would be the result.</p>
<p>Loading a batch of 25 conversations per request was taking, on average, 3960ms. Loading 1600 conversations I have on my local machine took about 4 minutes and 4 seconds:</p>
<p>Keep in mind that in order to be useful, there is no need for the user to load the entire 1600 articles! The first 25 batch is enough to get started, and the rest is here in case the user needs it.
That means that once the first request is done, the user can start using the app.</p>
<p>... but still, having to wait 4 seconds to get started is too much for Fernand, so we need to improve that.</p>
<p>So here come the second approach.</p>
<h2 id="heading-second-approach-loading-per-batch-of-types">Second approach: Loading per batch of types</h2>
<p>The previous method had a major drawback: We had to wait on the request to finish to start the second one, because of the "cursor" pagination. We couldn't just ask for the n, n+1, n+2 and n+3 items.</p>
<p>Waiting on one long request is not perfect so instead, we rewrote the backend to load the data as flat: Every related instances (such as messages, contacts, etc) would then be loaded by the frontend if/when needed.</p>
<p>Here's how we did it:</p>
<p>When loading the conversation listing, a request asking for the first 25 tickets would be sent. The data would be returned with IDs instead of linked data.
Then, the Frontend would push into a "batch" queue every ID needed to be loaded, per type (a queue of IDs for the Contacts, the Messages, the Related conversations, etc). That Batch system would then throw a new network request everytime the queue would have a size of 10 entries - or - when the loader would have finished.</p>
<p>Doing so has tremendous advantages :</p>
<ul>
<li>We would add on the Batch queue only the IDs we don't already have loaded or that aren't already on the queue. That means if we have multiple time the same ID (same Contact for instance), <strong>we would only load it once</strong>.</li>
<li>By loading only once, <strong>we reduce the amount of database query</strong></li>
<li>We also <strong>reduce the size of the payload</strong></li>
</ul>
<p>So definitely a bigger win.</p>
]]></content:encoded></item><item><title><![CDATA[Case study: Helpspace.io]]></title><description><![CDATA[Helpspace.io is a customer support product that got my attention on ProductHunt for a few reasons:

The name is easy to remember

The design of the landing page is nice

Customer support is a trending market


All in all, I think the makers behind He...]]></description><link>https://cnicodeme.com/case-study-helpspace-io</link><guid isPermaLink="true">https://cnicodeme.com/case-study-helpspace-io</guid><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Thu, 19 Sep 2019 14:31:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1684164874107/8a63da6d-f37f-4654-b452-d1c45aa0a82f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://helpspace.io">Helpspace.io</a> is a customer support product that got my attention on ProductHunt for a few reasons:</p>
<ul>
<li><p>The name is easy to remember</p>
</li>
<li><p>The design of the landing page is nice</p>
</li>
<li><p>Customer support is a trending market</p>
</li>
</ul>
<p>All in all, I think the makers behind Helpspace have a chance to do something nice with their tool, and that's why I decided to do a case study about it.</p>
<hr />
<p>I initially used <a target="_blank" href="https://groovehq.com">GrooveHQ</a> for quite some time. It was the best tool and worked nice, but they increased the price twice and ended up in a pricing strategy that wasn't for me ($19 per seat monthly, with a minimum of two seats ... For a sole founder, it doesn't make sense.)</p>
<p>I am currently using <a target="_blank" href="https://frontapp.com">Front</a> which provides a complete tool for a good price, but it has its few quirks. For instance, Twitter's integration comes at $20/month. It's too much when you are on the $9/month plan!!</p>
<p>Needless to say that I spent a fair amount of time comparing a lot of competitors, looking at their features, pricing, options, and differences.</p>
<p>So when I saw this newcomer, I was interested to know more about it, and the first impressions are positives.</p>
<p><em>An earlier version of this post was targetting the makers directly by saying "you", but it gave an arrogant tone to the document, and as such, has been rewritten. It wasn't my intention to give this feeling.</em></p>
<h3 id="heading-feedback">Feedback</h3>
<p>There are currently two main ways to offer a landing page:</p>
<ul>
<li><p>A short version with links that point to specific pages (features, pricing, etc)</p>
</li>
<li><p>Or a long version that contains everything. Bonus to point to also have a (not duplicated) dedicated page for each point for SEO and advanced explanations.</p>
</li>
</ul>
<p>Helpspace's landing page is nice because the design is lightweight but as a potential user, I think I was expecting more. The main lacking point I found was the absence of the price directly accessible. Let me explain:</p>
<p>I started to scroll, eager to see the features, and they started to convince me (great!), so my next thought was "how much?". But from there, I couldn't find the pricing options: I had to go all up to find it on the menu.</p>
<p>I would recommend either have the pricing options on the landing page directly (where a quick scroll brings it into view) or have a sticky menu to always have access to the pricing page.</p>
<p><em>Bonus point if you set the pricing page next to the features, as it was my next thought.</em></p>
<h4 id="heading-hero">Hero</h4>
<p>Also called Above the fold. It's the most important element of any website because visitors that are hesitating will leave your website if the hero doesn't convince them.</p>
<p>The image is great because it shows the potentially interested visitors what they'll get if they create an account, and it looks like the makers have made an interesting work in analyzing their persona:</p>
<p>The simplification of the image is made in such a way that for someone that knows how customer support works, it's easy to target the messages, the comments, the user's info, etc. Nice job!</p>
<p>One point that bothers me though, is the headline. If you scroll a little bit, you'll get this:</p>
<p><strong>Keep Your Support Under Control</strong><br /><em>With HelpSpace you will keep your incoming requests and questions in line and won't fail your customers!</em></p>
<p>This feels like a headline... too. There already is a headline in the hero section so I would recommend choosing between one of them. Both are great and this might depend on everyone, but what I highly recommend is to <strong>spend a lot of time working on the phrasing and the words used</strong>.</p>
<p>This utterly important because it's the first thing your visitor will read. They need to understand it without reading every word. If you don't catch their attention there, you might lose them even before they start scrolling!</p>
<h4 id="heading-video-explainer">Video explainer</h4>
<p>Customer support tools can quickly become feature-rich and having just an image on the landing page might not convey all the great features the service has to offer.</p>
<p>What I would recommend instead, is to keep this image (love it!) but add a "play" icon in the middle that suggests a video. When the visitor will click on it, a modal would open with a 1-1:30 minutes video of what Helpspace has to offer.</p>
<p>More and more visitors now rely on video to have a quick understanding of what a service does, and offering this as the first step for your visitors (remember, it's the first thing they'll see), will play an important role in your conversion rate.</p>
<h4 id="heading-features">Features</h4>
<p>One big issue of Helpspace is the competitive market. And other than fighting on the price, another possibility is to fight on the features, and how they are available. The features section of Helpspace works great because it's light but goes straight to the point.</p>
<p>The fact that it's not screenshots, but real <strong>interactive</strong> elements instead is a nice touch! But it can be a double-edged sword! Being used to have non-interactive elements, I thought they were animated gif and didn't bother to click on it.</p>
<p>The first feature is great because it shows you how Helpspace solves a frequent problem: How to handle multiple channels. The colored pills? Clever!</p>
<p>The second feature shows how neat the interface is when replying, and how the details (tag, date, etc) are displayed. This gives an insight into what to expect if you create an account.</p>
<p>Unfortunately, the interactive integration leaves me eager: I'd like to know what happens when I click on each item (tag, canned response, users?, three dots?) because clearly, I can (a "hover" effect is applied when you place your mouse over the icons), but when you click on it, nothing happens.</p>
<p>I'm pretty sure putting these elements here instead of the basic screenshot was a lot of work, but what I would recommend instead, is animated images (can be gif, or small videos), showing what can be done. The video format helps the visitors understand they won't be able to interact while showing them what can be done.</p>
<p>Since Helpspace is pretty recent, I'm sure that the dev team is hard in working on the set of features, and I would recommend them to put at least one more feature here once it's ready.</p>
<p>For information, here's what I would expect from a customer support service. Of course, depending on the vision of the founders, not everything will/might be implemented, but it can help :)</p>
<ul>
<li><p>Canned responses</p>
</li>
<li><p>Snooze (mark a message to be re-opened later)</p>
</li>
<li><p>Automations rules</p>
</li>
<li><p>Team management (assigning tickets for instance)</p>
</li>
<li><p>Search engine (with filters maybe, like GMail?)</p>
</li>
</ul>
<p>As for animated sequences, here are a few suggestions:</p>
<ul>
<li><p>Receiving a new email</p>
</li>
<li><p>(Quickly) replying to an email</p>
</li>
<li><p>Replying using a canned response</p>
</li>
<li><p>Adding a rule to automate the workflow</p>
</li>
<li><p>Assign the ticket to someone on your team</p>
</li>
<li><p>Change the status of an email (to a custom one maybe?)</p>
</li>
<li><p>Search over all your messages</p>
</li>
<li><p>Switch the view from open/archived/deleted</p>
</li>
</ul>
<h4 id="heading-pricing">Pricing</h4>
<p>As I've mentioned earlier, I would recommend having an "always accessible" way to view the pricing. It could be on a sticky menu that stays at the top or place the pricing directly on the page. The idea is to have visitors do the less work possible up to the account creation (and get that precious email).</p>
<p>By the way, on a side note, I would recommend to also have the pricing plan in the footer. It's not the case on Helpspace's website but it can only help ;)</p>
<p>On the pricing plan per se, here are a few suggestions I would do:</p>
<ul>
<li><p>I would offer two email channels instead of one. Why is that? Simply because with only one email channel, what is the difference between my inbox and Helpspace? By providing two, you start to differentiate.</p>
</li>
<li><p>Limiting to 5 emails on the Pro plan at this price is good, but convey the idea that Helpspace is only made for small businesses. If the idea is to attract bigger customers, I recommend offering some kind of "Enterprise plan" with higher limits.</p>
</li>
</ul>
<p>Pricing is hard. I recommend you to try - a lot - and don't hesitate to increase the prices too.</p>
<p>As for the enterprise plan, one possible way to make it work with bigger clients is to set a base price (say, $49 for 20 channels) and then mention a "+$2/m per channels after that".</p>
<h4 id="heading-dont-go-in-too-many-directions">Don't go in too many directions</h4>
<p>Customer support is a wide area and can integrate many many extra features. Helpspace suggests they are working on two future features (at the time of the writing):</p>
<ul>
<li><p>Ticket tasks</p>
</li>
<li><p>Knowledge base</p>
</li>
</ul>
<p>These are very interesting features, and clearly, features that might be asked in the long run, but what I would recommend to the Helpspace team instead, is to first focus on the core features of a customer support service.</p>
<p>Things like canned responses, rules, team assignments, tags, are - in my opinion - more important to implement. Then, once each of the core features are implemented and works great, and only then, I would recommend starting to add satellite features.</p>
<p>If you follow the features of your competitor, you will be just like them. If you are just like them, you won't differentiate and you will be lost in the crowd.</p>
<p>Here's what I would do (but again, this is my point of view):</p>
<p>I would focus on the missing features that are essential when handling support (automated responses via rules, canned responses, office hours, etc) and put the ticket tasks and knowledge base items in the pending todo list.</p>
<p>With only the core features, I would be able to lower the price. Yes, this is tricky because everyone says "increase your price", but in Helpspace case, they are in a very competitive market and the price will be a big decision maker since they are new. Take a look at this diagram:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684164913066/9cb8b09a-402e-4a9d-9e7f-e76f44c8dd60.png" alt class="image--center mx-auto" /></p>
<p>The idea behind this is to get a lot of new customers that are not looking for tons of features (... like knowledge base) but just want a nice interface to manage their support. Helpspace will fit perfectly right in. This will help you grow, get <strong>customer feedback</strong>, improve the service and add more core features or master the current one.</p>
<p>Once you will have reached a certain amount of customers and have implemented all the core features, you will then be able to work on the extensions, offering tickets, knowledge base and even more, and increase your price from there (or offer these as paid features).</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Helpspace looks nice and I think the makers are on the great track of having a really cool SaaS product. I hope my thoughts were helpful and I wish them good luck!</p>
]]></content:encoded></item><item><title><![CDATA[Generating PDF documents in Python — The easy way]]></title><description><![CDATA[Generating a PDF document can be hell. Here, we’ll try to make that simpler by using PDFShift.io powerful API. (Plot twist: I’m the founder 😲)
What if I told you it was easy as sending a POST request? Take a look:
response = requests.post(
    'http...]]></description><link>https://cnicodeme.com/generating-pdf-documents-in-python-the-easy-way</link><guid isPermaLink="true">https://cnicodeme.com/generating-pdf-documents-in-python-the-easy-way</guid><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Thu, 16 May 2019 17:45:11 GMT</pubDate><content:encoded><![CDATA[<p><em>Generating a PDF document can be hell. Here, we’ll try to make that simpler by using</em> <a target="_blank" href="https://pdfshift.io"><em>PDFShift.io</em></a> <em>powerful API. (Plot twist: I’m the founder 😲)</em></p>
<p><strong><em>What if I told you it was easy as sending a POST request? Take a look:</em></strong></p>
<pre><code class="lang-python">response = requests.post(
    <span class="hljs-string">'https://api.pdfshift.io/v2/convert/'</span>,
    auth=(api_key, <span class="hljs-string">''</span>),
    json={<span class="hljs-string">'source'</span>: raw_html}
)

<span class="hljs-keyword">with</span> open(<span class="hljs-string">'document.pdf'</span>, <span class="hljs-string">'wb'</span>) <span class="hljs-keyword">as</span> f:
    f.write(response.content)
</code></pre>
<p>See? Now, let me say the same thing, but longer:</p>
<p>As a developer, you might find yourself facing the needs to generate a PDF for your application. It could be an invoice to be sent by email, a report to share to your users, or a blog post to be shared to your user’s user.</p>
<p>One simple, efficient and quick way to do that is to first create your document in HTML format. You already master the HTML/CSS elements and can move them like a Jedi, so why not?</p>
<p>All you have to do is convert the HTML into PDF and voilà! Your work is done.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684165039229/da5c872b-4ef2-4f7a-8840-bc4392a43576.gif" alt class="image--center mx-auto" /></p>
<p>Once you have your HTML document ready, one way you can do to get your PDF is by using an API.</p>
<p><a target="_blank" href="https://pdfshift.io">PDFShift</a> provides just that, and getting a PDF is as easy as:</p>
<pre><code class="lang-bash">POST https://api.pdfshift.io/v2/convert/ -d <span class="hljs-built_in">source</span>={url}
</code></pre>
<p>The first thing you will need is an API key to generate the documents without watermark. <a target="_blank" href="https://pdfshift.io/register/">Create your account on PDFShift.</a> You will get your API key by email right after hitting submit and the free account will let you convert 50 documents per month.</p>
<p>👉 <a target="_blank" href="https://pdfshift.io/register/"><em>Create your account on PDFShift here.</em></a></p>
<p>We’ll then use the requests library to do the work. It allows you to make network request really easy and simplify all the tedious part of it.</p>
<p>Will start simple, converting your local HTML document to a PDF with no special stuff involved:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests

raw_html = <span class="hljs-literal">None</span>

<span class="hljs-keyword">with</span> open(<span class="hljs-string">'./document.html'</span>, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> f:
    raw_html = f.read()

api_key = <span class="hljs-string">'your_pdfshift_api_key'</span>

response = requests.post(
    <span class="hljs-string">'https://api.pdfshift.io/v2/convert/'</span>,
    auth=(api_key, <span class="hljs-string">''</span>),
    json={<span class="hljs-string">'source'</span>: raw_html}
)

<span class="hljs-comment"># We now ensure that the conversion was successful</span>
<span class="hljs-keyword">try</span>:
    response.raise_for_status()
<span class="hljs-keyword">except</span>:
    <span class="hljs-comment"># Here, we handle any error that might have occured</span>
    <span class="hljs-keyword">pass</span>

<span class="hljs-keyword">with</span> open(<span class="hljs-string">'result.pdf'</span>, <span class="hljs-string">'wb'</span>) <span class="hljs-keyword">as</span> fout:
    fout.write(response.content)

print(<span class="hljs-string">'Done'</span>)
exit(<span class="hljs-number">1</span>)
</code></pre>
<p>A few lines of code later, your application is now generating PDF documents.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684165065974/a533d9eb-1e15-4a53-b331-d09af16a096d.gif" alt class="image--center mx-auto" /></p>
<p>I could stop here, but I’m not sure you realized how lucky you are. So let me share my point of view.</p>
<h2 id="heading-why-is-this-better-than-using-wkhtmltopdfdockersomelibrary">Why is this better than using {wkhtmltopdf/docker/some_library}?</h2>
<p>Good question! One easy answer:</p>
<p><strong><em>headaches</em></strong>!</p>
<p>Installing a local software, running your own Docker instance or using a PDF library made for your programming language are all possibilities to generate PDF, but with all of these come a great bargain: maintenance.</p>
<p>You will have to update the software and library, making sure the processes haven’t stopped, or that your Docker server has shut down for an unexpected reason.</p>
<p>Moreover, you might not be able to do some advanced things like the one we’ll cover next.</p>
<h2 id="heading-ensuring-all-your-charts-are-correctly-loaded">Ensuring all your charts are correctly loaded</h2>
<p>Now, you are just getting started and you want to generate a PDF of your report to send to all your customers on a monthly basis.</p>
<p>The issue is that the PDF returned is made <em>before</em> the charts have all been loaded, making your PDF useless. PDFShift provides a parameter that let you wait on your document to be finished before continuing the conversion.</p>
<p>This is useful here because you can wait for all your charts to be rendered before generating the PDF, ensuring that the document you’ll get back are <em>perfect</em>. This parameter is wait_for and the value is just the name of a javascript function that will be globally present (think window).</p>
<p>Here’s the initial code, with the wait_for parameter:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests

raw_html = <span class="hljs-literal">None</span>

<span class="hljs-keyword">with</span> open(<span class="hljs-string">'./document.html'</span>, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> f:
    raw_html = f.read()

api_key = <span class="hljs-string">'your_pdfshift_api_key'</span>

response = requests.post(
    <span class="hljs-string">'https://api.pdfshift.io/v2/convert/'</span>,
    auth=(api_key, <span class="hljs-string">''</span>),
    json={
        <span class="hljs-string">'source'</span>: raw_html,
        <span class="hljs-string">'wait_for'</span>: <span class="hljs-string">'pdfshiftReady'</span> <span class="hljs-comment"># That's just the line we added ...</span>
    }
)

<span class="hljs-comment"># We now ensure that the conversion was successful</span>
<span class="hljs-keyword">try</span>:
    response.raise_for_status()
<span class="hljs-keyword">except</span>:
    <span class="hljs-comment"># Here, we handle any error that might have occured</span>
    <span class="hljs-keyword">pass</span>

<span class="hljs-keyword">with</span> open(<span class="hljs-string">'result.pdf'</span>, <span class="hljs-string">'wb'</span>) <span class="hljs-keyword">as</span> fout:
    fout.write(response.content)

print(<span class="hljs-string">'Done'</span>)
exit(<span class="hljs-number">1</span>)
</code></pre>
<h2 id="heading-processing-multiple-documents-at-once">Processing multiple documents at once</h2>
<p>Now imagine you have tons of documents to generate, one per user. Sending a POST request for each of them will take time and waiting for the result back might takes days of running process, hoping nothing will break in the middle!</p>
<p>On the other hand, what you can do is send the conversion in batch, up to 50 per request, and don’t have to wait for the result to come back.</p>
<p>Imagine you load your entire user's database and want to generate a custom report for each of them. This script will do exactly that and will be processed in a few minutes.</p>
<p>All you’ll need is a publicly available endpoint that will receive a POST request from us (we love POST) containing an URL to your generated PDF content!</p>
<p>We’ve taken this case and made a code just for you to see:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">from</span> database <span class="hljs-keyword">import</span> db  <span class="hljs-comment"># This is just for the example here</span>

api_key = <span class="hljs-string">'your_pdfshift_api_key'</span>

<span class="hljs-comment"># We could improve this part too</span>
users = db.execute(<span class="hljs-string">'SELECT id FROM users WHERE removed IS NULL'</span>).all()

<span class="hljs-comment"># Splitting users in chunks of 50:</span>
chunks = [users[i:i + <span class="hljs-number">50</span>] <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">0</span>, len(users), <span class="hljs-number">50</span>)]

<span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> chunks:
    <span class="hljs-comment"># Creating custom URL for each of our users</span>
    urls = [<span class="hljs-string">'https://your-website.com/report/user?id={0}'</span>.format(x[<span class="hljs-string">'id'</span>]) <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> c]

    response = requests.post(
        <span class="hljs-string">'https://api.pdfshift.io/v2/convert/'</span>,
        auth=(api_key, <span class="hljs-string">''</span>),
        json={
            <span class="hljs-string">'source'</span>: urls,
            <span class="hljs-string">'webhook'</span>: <span class="hljs-string">'https://your-website.com/report/webhooks/pdfshift'</span>
        }
    )

    <span class="hljs-comment"># We now ensure that the conversion was successful</span>
    <span class="hljs-keyword">try</span>:
        response.raise_for_status()
    <span class="hljs-keyword">except</span>:
        <span class="hljs-comment"># Here, we handle any error that might have occured</span>
        <span class="hljs-keyword">pass</span>

print(<span class="hljs-string">'Done'</span>)
exit(<span class="hljs-number">1</span>)
</code></pre>
<h2 id="heading-another-real-case-study-sending-an-invoice-by-email">Another real case study, sending an invoice by email</h2>
<p>When I first started PDFShift, it was because of a recurring issue I had. I wanted my customers to receive a real, nicely designed PDF invoice of their purchase.</p>
<p>The issue was that I was using a library at the time that required tons of dependencies that had to be installed as a system package directly. Without them, the library wouldn’t work (and even with some of them, it would still fail). Setting all up was costing me a few hours every time and I was getting bored.</p>
<p>So I like to use this example as a real case study because … well … it’s a real case study :)</p>
<p>As you’ll see, the code is a bit long, but converting HTML to PDF only takes a few lines of code, the rest is just email stuff (but interesting, still!):</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">import</span> email, smtplib, ssl

<span class="hljs-keyword">from</span> email <span class="hljs-keyword">import</span> encoders
<span class="hljs-keyword">from</span> email.mime.base <span class="hljs-keyword">import</span> MIMEBase
<span class="hljs-keyword">from</span> email.mime.multipart <span class="hljs-keyword">import</span> MIMEMultipart
<span class="hljs-keyword">from</span> email.mime.text <span class="hljs-keyword">import</span> MIMEText

pdfshift_api_key = <span class="hljs-string">'your_pdfshift_api_key'</span>

raw_html = <span class="hljs-literal">None</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">'./templates/invoice.html'</span>, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> f:
    raw_html = f.read()

response = requests.post(
    <span class="hljs-string">'https://api.pdfshift.io/v2/convert/'</span>,
    auth=(pdfshift_api_key, <span class="hljs-string">''</span>),
    json={
        <span class="hljs-string">'source'</span>: raw_html,
    }
)

<span class="hljs-comment"># We now ensure that the conversion was successful</span>
<span class="hljs-keyword">try</span>:
    response.raise_for_status()
<span class="hljs-keyword">except</span>:
    <span class="hljs-comment"># Here, we handle any error that might have occured</span>
    <span class="hljs-keyword">pass</span>


<span class="hljs-comment"># Create a multipart message and set headers</span>
message = MIMEMultipart()
message[<span class="hljs-string">"From"</span>] = <span class="hljs-string">'support@your-service.com'</span>
message[<span class="hljs-string">"To"</span>] = <span class="hljs-string">'customer@gmail.com'</span>
message[<span class="hljs-string">"Subject"</span>] = <span class="hljs-string">'Thank you for your purchase! Here\'s your invoice!'</span>

<span class="hljs-comment"># Add body to email</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">'./emails/invoice.txt'</span>, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> f:
    message.attach(MIMEText(f.read(), <span class="hljs-string">"plain"</span>))

part = MIMEBase(<span class="hljs-string">"application"</span>, <span class="hljs-string">"octet-stream"</span>)
part.set_payload(response.content)  <span class="hljs-comment"># Here! We attach PDFShift binary response!</span>

encoders.encode_base64(part)

part.add_header(
    <span class="hljs-string">"Content-Disposition"</span>,
    <span class="hljs-string">"attachment; filename= invoice.pdf"</span>,
)

message.attach(part)
complete_email = message.as_string()

<span class="hljs-comment"># Log in to server using secure context and send email</span>
context = ssl.create_default_context()
<span class="hljs-keyword">with</span> smtplib.SMTP_SSL(<span class="hljs-string">"smtp.mail-provider.com"</span>, <span class="hljs-number">465</span>, context=context) <span class="hljs-keyword">as</span> server:
    server.login(<span class="hljs-string">'your_email'</span>, <span class="hljs-string">'your_password'</span>)
    server.sendmail(<span class="hljs-string">'support@your-service.com'</span>, <span class="hljs-string">'customer@gmail.com'</span>, complete_email)

exit(<span class="hljs-number">1</span>)
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Creating a PDF document can be tedious when we don’t have the right tool at our disposal. On the other hand, most of us have been writing HTML/CSS documents for years now and can quickly build a page this way.</p>
<p><a target="_blank" href="https://pdfshift.io">PDFShift</a> lets you convert these HTML documents in PDF in just a simple POST request and works everywhere. With it, you don’t have to worry anymore about maintenance, updates, and compatibility issues.</p>
<p><em>At</em> <a target="_blank" href="https://pdfshift.io"><em>PDFShift</em></a><em>, our motto is the same as the Unix philosophy: We do one thing, and we do it well.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684165094689/6704de6c-10ca-43e3-b449-fc5a14710433.gif" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[The story behind ImprovMX]]></title><description><![CDATA[I always had a passion to work with emails. SMTP, POP, and IMAP are filling my stomach with butterflies (something must be off in my brain) and after having sold VoilaNorbert, I was still eager to build Python code that connects to SMTP servers throu...]]></description><link>https://cnicodeme.com/the-story-behind-improvmx</link><guid isPermaLink="true">https://cnicodeme.com/the-story-behind-improvmx</guid><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Wed, 15 May 2019 10:40:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1684165212188/b1cd5a61-00b3-4ec9-bbc7-f0f6ae7735ab.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I always had a passion to work with emails. SMTP, POP, and IMAP are filling my stomach with butterflies (something must be off in my brain) and after having sold <a target="_blank" href="https://voilanorbert.com">VoilaNorbert</a>, I was still eager to build Python code that connects to SMTP servers through sockets…</p>
<p>I’ve been using and I knew about <a target="_blank" href="https://improvmx.com">ImprovMX</a> for many years and it was, for me, one of the tools that were always present. The issue was that the support was non- existent and if you had the misfortune of having a typo in your email, you could say goodbye to using <a target="_blank" href="https://improvmx.com">ImprovMX</a> (true story).</p>
<p>For this reason, I searched the owner of <a target="_blank" href="https://improvmx.com">ImprovMX</a> and offered to acquire it in the objective to improve and grow the service. Turns out he was open to discussion and we quickly found common ground. Thank you, Alex! :)</p>
<p>I was excited to acquire <a target="_blank" href="https://improvmx.com">ImprovMX</a>, provide great customer support and improve, iterate and make <a target="_blank" href="https://improvmx.com">ImprovMX</a> a best in class for email forwarding.</p>
<p>Once the transition was complete, I initially started changing completely how the emails were handled. It was initially using Postfix, but we decided to scratch this and build a customized suite in Python that allows us to do almost everything we wanted, including being able to know how many emails are in forwarding chain (how many we received, how many got accepted and how many got delivered).</p>
<p>We had to implement many systems to avoid spam abuse, greylisting and blacklisting, and we are getting better and better over time.</p>
<p>After that, we worked with Antoine on making a new website to show our commitment to providing a great service. We focus on what we do best: Making a simple, yet powerful service for everyone to use. You can get started quickly and forwarding emails just takes a few steps and can now be done in a few minutes.</p>
<p>Email, in general, is not a piece of cake. Along with all the intricacies of having to manage SPF, SRS, DKIM, DMARC, you also have to fight against spammers, getting blacklisted from time to time because some spams manage to get through your service, and tons of other vicious things that make email …. <em>so awesome</em>!</p>
<p>What excites me the most with <a target="_blank" href="https://improvmx.com">ImprovMX</a> is the reach. We are just getting started here and forwarding is just the beginning. We have so many projects in mind related to email that we need to define a clear roadmap for the future and be sure to play our cards well.</p>
<p>The way we structured our servers and how we wrote the code will allow us to extend and be able to, in the near future, provide many great features to help our customers, help developers and provide more than just email forwarding.</p>
<p>We are just getting started, and we are eager!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684165227022/83701f6c-4b7c-43f8-b913-1fb1dba5fe4d.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[On difficulties to monetize a marketplace]]></title><description><![CDATA[We recently switched Transferslot to a new model, one that only accepts profitable projects. We know this is a big step and will greatly reduce the number of submissions, but on the other hand, the products that will be published will be of better qu...]]></description><link>https://cnicodeme.com/on-difficulties-to-monetize-a-marketplace</link><guid isPermaLink="true">https://cnicodeme.com/on-difficulties-to-monetize-a-marketplace</guid><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Fri, 16 Nov 2018 12:15:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1684165277998/f6561a6e-7161-4c55-b5d9-62921b945561.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[

<p>We recently switched <a target="_blank" href="https://transferslot.com">Transferslot</a> to a new model, one that only accepts profitable projects. We know this is a big step and will greatly reduce the number of submissions, but on the other hand, the products that will be published will be of better quality, with a beginning of market validation and a potential for growth that are more interesting.</p>
<p>While making this decision, we had a long (and unfinished) talk about how we could monetize <a target="_blank" href="https://transferslot.com">Transferslot</a> in such a way that is a win-win for everyone.</p>
<p>Many ideas came up, and I think it’s interesting to share them here. It might help other owners of different marketplaces along with giving you an idea of what we thought of each possibility.</p>
<h3 id="heading-the-obvious-one-listing-fee">The obvious one: Listing fee</h3>
<p>Of course, we could do like the main one does; asking for a payment when submitting a project.</p>
<p>Having been on the seller side of Flippa, I know that I didn’t mind much about paying for listing my project. I had a fair idea of how much it would cost, how much I would gain from it so I was able to make a rational decision on how which option to choose.</p>
<p>The issue is that it’s 1) not the case for everyone, 2) doesn’t guarantee you a successful sale.</p>
<p>The point 2 is, for me, the most important one here! We don’t think that it’s fair as a marketplace provider to ask for payment for something that might not work. We want our seller to be happy if they pay us, not crossing fingers and hope for the best.</p>
<h3 id="heading-paying-for-options">Paying for options</h3>
<p>One of the things we offer right now, and one that I like quite the most, is offering the listing for free but offering options that aren’t free.</p>
<p>Two things we currently offer are the option to be private, and being on a dedicated newsletter.</p>
<p>The dedicated newsletter is really interesting because it clearly shows the interest for the seller (increased visibility) and can easily justify the cost (currently at 50$).</p>
<p>That’s one strategy that Flippa implements too (even though their price for having a dedicated newsletter are way higher, but they also have way more subscribers!).</p>
<h3 id="heading-limiting-the-number-of-buyers-requests">Limiting the number of buyer’s requests.</h3>
<p>One other option we discussed, and almost implemented, was to allow for three buyer’s requests to be sent to the seller, then block the future messages.</p>
<p>If the seller wanted to read them, he would have to pay us to “unlock” the pending messages.</p>
<p>At first, it looks like the listing fee option; There is still no way that the sale will be successful, and it’s true. The reason why we still thought it would be a good idea was that with the listing fee, you don’t have a clue about the potential of your sale. With this solution, if you have more than 3 requests, it means you got some interests, and thus a higher chance to sell.</p>
<p>One con about it is that if the seller doesn’t decide to pay, the potential pending buyers won’t receive a response and will think that either the seller or <a target="_blank" href="https://transferslot.com">Transferslot</a>, ignore their messages. This is not great. (Of course, we could handle the pending message after a certain time, but it requires more work that aren’t really necessary in that case).</p>
<p>Three alternatives are possible in this possibility, such as :</p>
<ul>
<li>Allowing for 3 free messages before blocking the next ones</li>
<li>Hiding name and email from the beginning. The seller would have to pay to get the detailed information (same issue as the listing fee)</li>
<li>Block from scratch (same issue as the listing fee)</li>
</ul>
<h3 id="heading-relying-on-trust">Relying on trust</h3>
<p>This is one of my favorites, even though it has a high chance of not working.</p>
<p>The idea is simple: We would ask our sellers to let us know once their project is sold (they are kind of required if they want the project to be removed/updated on <a target="_blank" href="https://transferslot.com">Transferslot</a>). Once we acknowledge that the sale was successful thanks to <a target="_blank" href="https://transferslot.com">Transferslot</a>, we can request the seller to pay us a fee (15% for instance) of the sold price.</p>
<p>The issue here is that:</p>
<ol>
<li>We can’t enforce a seller to pay us</li>
<li>They can lie, saying they sold outside of <a target="_blank" href="https://transferslot.com">Transferslot</a>.</li>
</ol>
<p>That’s the reason I named this part “relying on trust”. Maybe not all the sellers will pay, but if a few do, it will still be beneficial.</p>
<h3 id="heading-being-legal">Being legal</h3>
<p>Improving the “Relying on trust” part, we could require our sellers to sign a document which engages themselves on having to pay a fee if we successfully help them make a sale. Having to sign this document will enforce the seriousness of it and will decrease the risk of user avoiding to pay the fee.</p>
<h3 id="heading-being-the-middleman">Being the middleman</h3>
<p>Another solution would be that instead of buyers reaching out directly to sellers on <a target="_blank" href="https://transferslot.com">Transferslot</a>, it would be us that receives the messages. We would either reply directly if we already know the answer or reach out to the seller for more details.</p>
<p>Of course, buyers could contact the seller directly from their website since the data aren’t hidden, but we would rely on the fact that it’s easier for the buyer to just fill the form on Transferslot’s website instead of having to search for contacts credentials.</p>
<p>One of the cons here is that it will require a lot more work on our side, with a low response time. But on the pros, working as a middleman will give us the chance to better handle the sales, eventually catch bad sellers faster, and requesting a fee upon successful sale.</p>
<h3 id="heading-being-an-escrow-service">Being an escrow service</h3>
<p>Finally, one of the most advanced way we can earn money while sharing a win-win with our customers.</p>
<p>By implementing two of the discussed options here; Being legal and being the middleman, we will ensure that the transaction occurs and it will help us identify any issues, work with both the seller and the buyer.</p>
<p>This will also provide a chain of trust as we will manage the whole transaction and ensure everything goes smoothly. Along the transaction, taking a fee will be easy and evident.</p>
<p>But there are big disadvantages to doing so. The first one is the time involved. We won’t handle one project at a time, but multiple. Managing the discussions between the sellers and buyers, hoping we don’t mix the projects and provide incorrect numbers.</p>
<p>Moreover, by being in the middle at this level, we engage ourself legally too. If the sale goes south and one member has already invested in it (paid the price or send all their code for instance), all the issues might fall on us. At this level, I’m not talking about small angry emails, but a potential lawsuit!</p>
<p>Even though that’s clearly the best option there, it’s also the riskier. That’s why we haven’t done it yet.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>We still don’t know our next move regarding Transferslot. The difficulty here is that we are in the middle, between Flippa and <a target="_blank" href="https://feinternational.com/">FEInternational</a>, and we can’t implement all the options.</p>
<p>I think we need to find what service we want to provide from A-Z and use a revenue model that fits greatly with the service. There are a lot of options here, some more honest than others, and that a key point — at least for me — honesty.</p>
<p><em>Thanks for reading, I hope it was helpful for you. If you have any other ideas we could implement, I’d be happy to discuss them with you. It’s also not impossible that we try some of the mentioned methods in the future on <a target="_blank" href="https://transferslot.com">Transferslot</a>, and see which one works best.</em></p>
]]></content:encoded></item><item><title><![CDATA[Excitation is killing productivity]]></title><description><![CDATA[I don’t know about you, but when I’m working on a project, I sometimes have a few moment of excitations thinking about the future, how the project will be, how that specific feature I’m working on will trigger some great feedback when I’ll start to t...]]></description><link>https://cnicodeme.com/excitation-is-killing-productivity</link><guid isPermaLink="true">https://cnicodeme.com/excitation-is-killing-productivity</guid><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Tue, 01 Aug 2017 17:18:57 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684165342306/1541c858-ab2d-4fc3-ac84-64a2bdc572e1.gif" alt class="image--center mx-auto" /></p>
<p>I don’t know about you, but when I’m working on a project, I sometimes have a few moment of excitations thinking about the future, how the project will be, how that specific feature I’m working on will trigger some great feedback when I’ll start to talk about it…</p>
<p>Even though those excitations are what motivate me to work on the project, they tend to kill my productivity. I tend to stray away from the current work and start to wander in my imaginary land.</p>
<p>Moreover, those imaginary moments are just that, imaginary! And often, the reality is harsher, that feature will go unnoticed and that’s all.</p>
<p>We, Humans, are creature that doesn’t live in the present. We live in the past or the future. We always are thinking about something that happened, or how thing will happen. And the situation here is a typical example of that.</p>
<p>The issue with this is that those moments are a thing from the far away future and I tend to forget about all the hard work in-between. That specific feature is not ready yet, and once it will be, after hours of work, I will still have to genuinely find a solution to promote it in the best way possible. Meaning I will have to work more in order to achieve what my brain thought about the future three months from now.</p>
<p>It’s easy to become distracted by the things that motivate us. It’s easy to become distracted by the great future that is in front of us, but in order to achieve it, we need to think about the hard work in between.</p>
<p>Success doesn’t happen overnight.</p>
]]></content:encoded></item><item><title><![CDATA[The end of AdSense?]]></title><description><![CDATA[Wow, this is a strong title, isn’t it? And I’m pretty sure Google will find a way to avoid this, as it is one of it’s best source of revenue.
But stay with me while I share my point of view …
More and more people are starting to ignore everything tha...]]></description><link>https://cnicodeme.com/the-end-of-adsense</link><guid isPermaLink="true">https://cnicodeme.com/the-end-of-adsense</guid><dc:creator><![CDATA[Cyril N.]]></dc:creator><pubDate>Mon, 19 Jun 2017 10:43:10 GMT</pubDate><content:encoded><![CDATA[<p>Wow, this is a strong title, isn’t it? And I’m pretty sure Google will find a way to avoid this, as it is one of it’s best source of revenue.</p>
<p>But stay with me while I share my point of view …</p>
<p>More and more people are starting to ignore everything that looks like ads. When Google was placing advertising on the right of a search result, it was proven that people started to completely remove it from their vision, simply ignoring it. It’s called <a target="_blank" href="https://en.wikipedia.org/wiki/Banner_blindness">banner blindness</a>!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684165490510/dc666519-b727-4315-842a-fc6c3fddd2b3.jpeg" alt class="image--center mx-auto" /></p>
<p>Now that ads are placed on a top of a search result, there is two behaviour occurring. Click on it, to make the company pay for nothing, or avoid clicking on it because having ads leaves an oddly feeling of being duped.</p>
<p>Moreover, Ad-blockers are on the rise and many users are installing them every day … to the point that even browser makers, including Google, are thinking of <a target="_blank" href="https://techcrunch.com/2017/04/19/google-said-to-be-planning-a-built-in-ad-blocker-for-chrome/">implementing them directly within their browser</a>.</p>
<p>Many website owners that make money via banner advertising are seeing <a target="_blank" href="https://news.ycombinator.com/item?id=14577016">big drops every few months</a>. And in order to counter this, one of the main example I can think of is used by the news website: They desperately try to force you to disable your ad-blocker — This is not sustainable.</p>
<p>But don’t get me wrong, the end of AdSense doesn’t mean the end of ads, absolutely not. Marketers will find an alternative way to send you their products, and one of the most efficient ways is, for me, the integrated ads.</p>
<p>Many startups and a few big companies are starting to integrate into their design, ads that don’t appear like this at first sight. I think this way is better, mainly because it doesn’t disturb the initial reading of the page (quick overview) and once you reach the advertising, you identify it and skip it. That’s it! It doesn’t clutter your design, design requires much overhead for your visitors, and can be simply ignored when reached.</p>
<p>Some of the websites that do it well include <a target="_blank" href="https://www.indiehackers.com/">IndieHackers</a> and <a target="_blank" href="https://sidebar.io/">Sidebar</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684165398152/34c7d33a-ed9a-4516-97a1-a7d3356da28f.png" alt="How Sidebar implement Ads in a better way" class="image--center mx-auto" /></p>
<p>Moreover, this kind of integration provides another great source of revenue. Instead of working with an advertising company like AdSense that will provide you with a small CTR/CPM while charging too much for the buyer, you can directly negotiate with someone that wants to appear on your website, and quickly make a few hundred. Everyone is happy!… well, except AdSense, which adds more argument to my post ;)</p>
<p>I do think that we will tend more and more toward this model of revenue, at least until someone finds a better way to provide us with advertising that doesn’t annoy users while still providing revenues to the host.</p>
<p>We will see what the future holds.</p>
<p>Until then …</p>
]]></content:encoded></item></channel></rss>