Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    Always put side effects last

    wp content uploads 2016 10 salesforce tower panorama 1024x358

    You know how some lessons you can only learn from experience? This is such a lesson. I'm sharing it with you so that I may remember it better.

    Let's say you're building a system for drip campaigns. Because that's how I learned this lesson.

    Your system is given a set of leads. You have to send them a series of messages, update metadata in your database, and keep the sales CRM (customer relationship management) system up-to-date so when a lead eventually replies to your message, a sales person knows what to do.

    Hundreds of these systems have been developed by thousands of developers. You're building your own because startups.

    The core of your system is a send_next_message function. Something like this 👇

    def send_next_message(lead) campaign = lead.campaign Twilio.send_message(lead, campaign.message) CRM.update_data(lead) campaign.advance_to_next_message end

    It makes sense, right? You take the drip campaign, send the current message with Twilio, update some metadata on your CRM, then advance your campaign to the next message.

    Works perfect 👌

    You run it in a cronjob once a day. Like this:

    def send_campaigns Lead.active.each do |lead| send_next_message.perform_async(lead) end end

    You go through active leads, pick their campaign, and spawn a new background worker with perform_async. Eventually, your queue will run the code and send your campaign.

    If anything goes wrong, each campaign is isolated in its own worker. Errors don't affect other campaigns. 👌

    Even better, if something goes wrong while sending the campaign, you can retry the worker.

    And you've just spammed your leads and made them angry

    Let's review our send_next_message code.

    def send_next_message(lead) campaign = lead.campaign Twilio.send_message(lead, campaign.message) CRM.update_data(lead) campaign.advance_to_next_message end

    An error can happen at any point here.

    Maybe something goes wrong when you read campaigns from the database. The worker terminates on 1st instruction, and all is good.

    Things can go wrong when talking to Twilio as well. Their service could be down, your internet could act up, or something else entirely. The worker terminates on 2nd instruction, and all is still good.

    What if things break when updating your CRM? Third party service, complex set of instructions with multiple API calls, a lot can go wrong.

    Worker terminates on 3rd instruction.

    Now you're in trouble. You've already sent a message to your user, but you weren't able to save that info. Your system doesn't know sending happened, and your CRM doesn't know either.

    When the worker retries, you send the message again.

    And again. And again. Until the whole worker succeeds.


    Your lead just got a bazillion messages, and they're upset.

    Here's what you should do instead 👇

    def send_next_message(lead) campaign = lead.campaign message = campaign.message CRM.update_data(lead) campaign.advance_to_next_message Twilio.send_message(lead, campaign.message) end

    Fetch campaign, save the message in a local variable. Then update data in your CRM. Now if updating the CRM fails, you can keep retrying until it succeeds.

    Updating the CRM was least reliable in my experience.

    After updating the CRM, you update your local database. This is a reliable operation, but the most common source of programmer errors and nil failures. It happens.

    Break fast and move things, right? :)

    At the end, when you're absolutely certain all your data is updated, you send the message to your lead. This is least likely to fail, and if it does, screw it.

    With this approach, you are guaranteed never to spam your leads. The worst that could happen is that you advance the campaign too early and they get 4 messages instead of 5 and your sales pitch is less polished.

    That's okay 🙏🏻 Your copy is robust, and annoying people is bad.

    And now I know why sales automation companies have so much money. This stuff is hard. 😅

    PS: There are ways to further improve your worker. You could do your DB first, then rollback if an error occurs. You could try to rollback changes on the CRM if sending fails etc. But that gets complicated fast.

    Published on January 10th, 2018 in Startups, Technical

    Did you enjoy this article?

    Continue reading about Always put side effects last

    Semantically similar articles hand-picked by GPT-4

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.

    Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.

    Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.

    Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev

    Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization

    Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections

    Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog

    Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️

    Created by Swizec with ❤️