Using Lambda@Edge to setup server-side redirects on a Cloudfront+S3 static site

As an engineering team at @Close, we’re trying to write more about what we are learning/doing. I recently got to dabble in a little coding project, so I wrote it up. This post originally appeared on the Close Engineering Blog here.

We host our main marketing website,, as a static site (in our case, generated via Lektor) served by AWS. We store all the staticly generated files in S3 and have a Cloudfront distribution in front as our CDN.

Occasionally, we’ll need to setup a redirect, to point an old URL to a new URL, for example when combining two pages.

In S3 hosted static websites like ours, there were basically two main ways to accomplish redirects.

Approach 1: HTML meta tag redirects

The first is to do HTML meta redirects, by putting this tag on the page to redirect away from:

<meta http-equiv="refresh" content="0; url=" />

Whether your hand-code each redirect in this way, or use your static site generator to help (e.g. Lektor’s support for Redirects), the result is the same – redirects that happen fully client-side.

While this approach is convenient since everything is 100% static, it can be difficult to maintain in a large website and has real downsides for both performance and SEO compared to server-side redirects.

Approach 2: S3 Static Website Hosting Redirects

S3 does have some support for server-side redirects, which we were using in addition to meta tags previously described.

These redirects do solve the performance and SEO concerns compared to meta redirect tags.

Here’s an example of that XML format (configured in AWS Console under your S3 bucket > Properties > Static website hosting > Redirection Rules):


Problems with this approach included:

  • Their XML syntax is verbose
  • Difficulty redirecting to URLs that contain querystrings (details)
  • Desire for our marketing team to be able to setup webpage redirects without AWS access

So, it was time for a new approach.

Approach 3: Using Lambda@Edge and Cloudfront to do server-side redirects

Lambda@Edge is a feature of Cloudfront that allows you to run serverless functions to tweak the HTTP requests or responses between Cloudfront and your Origin or visitor.

We had recently deployed an extremely simple Lambda@Edge function on our Cloudfront distribution in order to force visitors to the HTTPS version of our site by adding the HSTS header to all responses.

We could then build upon this to create a user-friendly system for managing redirects.


I envisioned a very simple system like the following:

  • Keep a redirects.json file in the same GitHub repository as rest of our website, which our marketing team could easily maintain (with some instructions nearby).
  • Have a Lambda@Edge function attached to our Cloudfront distribution do server-side redirects based on this file.
  • Setup our CI/CD pipeline (in CircleCI) so that changes to redirects.json would get deployed automatically.

Here’s the redirects.json format I went with:

  "/jobs": { "to": "", "statusCode": 301 },
  "/resources/the-follow-up-formula": { "to": "/resources/followup", "statusCode": 301 },
  ... etc. ...

(Just be careful that your last entry does not contain a trailing comma.)

Setting up the Lambda@Edge function

Here’s the cheatsheet for creating the function properly:

  1. From the Lambda section of the AWS console, create a new Lambda function and give it a name like cloudfront-site-redirects.
  2. Choose the Node.js 10.x runtime, because that’s currently the latest that Lambda@Edge supports (despite regular Lambda functions supporting Node.js 12.x).
  3. Under “Permissions”, choose “Create a new role from AWS policy templates” and search for “Basic Lambda@Edge permissions (for Cloudfront trigger)”, which will setup a good IAM role for you (do not choose “Create a new role with basic Lambda permissions” which is insufficient).


Add code for the Lambda function

Ours ended up looking something like this:

const REDIRECTS_DATA_REGION = 'us-east-1';
const REDIRECTS_DATA_OBJECT = 'config/redirects.json';

const AWS = require('aws-sdk');

const s3 = new AWS.S3({ region: REDIRECTS_DATA_REGION });

async function readFile(bucketName, filename) {
  const params = { Bucket: bucketName, Key: filename };
  const response = await s3.getObject(params, function (err, data) {
    if (err) {
      console.error('s3.getObject error: ' + err);
  return response.Body.toString();

async function getRedirectsData() {
  let data;
  try {
    data = JSON.parse(txt);
  } catch (e) {
    console.error('getRedirectsData parse failed', txt, e);
    data = {};
  return data;

const supportedStatusCodes = {
  301: 'Moved Permanently',
  302: 'Found',
  307: 'Temporary Redirect'

exports.handler = async(event, context) => {
  // Get the incoming request and the initial response from S3
  const { request, response } = event.Records[0].cf;
  // Enable HSTS
  response.headers['strict-transport-security'] = [{ key: 'Strict-Transport-Security', value: 'max-age=63072000' }];
  // request.uri is just the URL path without hostname or querystring
  let path = request.uri
  // Cut off trailing slash to normalize it
  if (path.slice(-1) === '/') {
    path = path.slice(0, -1);
  const redirectsData = await getRedirectsData();
  const redirectData = redirectsData[path];
  // See if a redirect exists & sanity check that the redirect object is what we expect
  const shouldRedirect = !!(redirectData && && supportedStatusCodes[redirectData.statusCode]);
  if (shouldRedirect) {
    // Note: We can't completely create a new set of headers even for a redirect
    // because Cloudfront fails painfully if you set (or clear) a "read-only" header.
    // For Origin Response triggers, that includes leaving "Transfer-Encoding" alone.
    response.headers['location'] = [{
      key: 'Location',
    return {
      status: redirectData.statusCode,
      statusDescription: supportedStatusCodes[redirectData.statusCode],
      headers: response.headers,
  return response;

Give the IAM role access to your S3 bucket

On the Lambda page, scroll down to find “Execution role” which gives you a link to the IAM role that was auto-generated for you. Now we are going to expand this role by attaching an additional Policy so it also has permission to access the S3 bucket containing our redirects.json file.

    "Version": "2012-10-17",
    "Statement": [
            "Effect": "Allow",
            "Action": [
            "Resource": [

If your S3 bucket isn’t public, you may need to change its policy too.


From your Lambda function’s page:

  • Click “Save”
  • Go to Actions > Deploy to Lambda@Edge
  • Choose the Cloudfront distribution to attach this to, and set it up using Origin response as the trigger.

We choose “Origin Response” because this allows Cloudfront to cache the redirect itself similar to how it would cache any other response from the Origin.

Then, head to your Cloudfront distribution in AWS. You’ll notice its status is “InProgress” as the changes are propagated. You’ll need to wait for this, and you’ll also need to create an Invalidation (probably for /*) and wait for that too. (This process can take several minutes or more. Ugh.)

Finally, if everything is working properly, you can test out your new server-side redirects!

$ curl -I                
HTTP/2 301 
location: /resources/followup

Continuous deployment

Finally, just make sure that your regular deployment process uploads redirects.json to your S3 bucket (if it’s not already) and that a Cloudfront invalidatoin is created. For us that meant having a few lines in our deployment script like:

aws s3 cp config/redirects.json s3://
aws configure set preview.cloudfront true
aws cloudfront create-invalidation --distribution-id THE_ID_HERE --paths '/*'

Doing so has allowed our marketing team to have simple control over server-side redirects on our site.

Troubleshooting and other learnings

  • As mentioned above, troubleshooting on real deployments can be a time-consuming process. Therefore, you can use a “Test Event” to simulate your code way faster than deploying and waiting for invalidation each time.
  • To see if a Cloudfront distribution is using Lambda@Edge, go to the distribution > Behaviors > Edit and at the very bottom you’ll see the ARN with function name & version listed.
  • When publishing a new Lambda version, you can leave the Name blank and it will autoincrement a version number.
  • From your Lambda function, you can find a link to get to the CloudWatch Logs, which can be very helpful for debugging.
  • In Cloudfront Logs: Look at latest Log Groups for both “LambdaEdge” as well as name of your function.
  • You can only have 1 Lambda function deployed per trigger (e.g. for Origin Response) per distribution, meaning that the lambda function name should probably be specific to that distribution. (At first I thought I could add a second independent function on top of the existing HSTS one. That doesn’t work, however they do have a “Layers” feature I didn’t explore).
  • Lambda functions can use async/await in Node 10.x, so it’s nice to use those despite most online examples using callbacks.

Feel free to hit me up at @philfreo with any comments or questions!

Add A Comment

When, Who, How to Hire an Engineering Team (including Hiring Remotely)

This is the blog post version of a talk I gave at True University in Berkley, CA in June 2018 to help early stage startups scale their engineering teams from 2 to 20.

Hiring is hard. Really hard. I’ve been hiring on small startup engineering teams for the past 8 years (most recently grown Close’s engineering team from 2 to 14) and hiring really great engineers never gets easy. But it does get easier with practice and the right strategy.

Here I’m going to share some things I’ve learned about when and how to find the right people for your stage, how to structure a hiring process, and distributed/remote work. And most importantly: mistakes I’ve made along the way at

When to hire

There are just two simple rules to follow about when to start hiring your engineering team.

Don’t hire too early (rule #1)

Hiring is hard. It’s a REALLLLLLLY hard, slow, painful process. Seriously, you don’t want to go through it before you have to. Avoid the pain as long as you can!

People are really expensive ($$$). Generally nothing in your startup will cost more money or take up more of your time and attention than… people.

Do the job yourself first. Be busy. Learn and do all the things! If you do everything yourself for a while, you’ll be in better shape to understand what really needs doing. You’ll be able to write a better job description, and you’ll be better at identifying a good person to fill the role when the time comes.

Small = fast & nimble. As much as everyone tries to avoid it, there’s just always inherent communication and process overhead of having a larger team. Enjoy moving as fast as possible while you’re small.

Focus on what matters: building a product that people love. Spending your time on hiring activities (job posts, resume screening, phone screens, in-person interviews, job offers, great employee onboarding, etc.) – none of these directly matter to your business! What matters first is finding product-market fit with a product that people love. Customers paying you money.

At Close, we got to company profitability making a few million dollars in annual recurring revenue, all with a 3 person Product/Engineering team (with total company size of 6). Take advantage of your small team size as long as you can!

Don’t hire too late (rule #2)

Because it takes forever. Remember how I said hiring is a really hard, slow, painful process? This means that if you wait too long to start hiring… by the time you really accomplish your hiring goals, it will take you even longer than you’d think to add more people to the team.

Our mistake: At, we started hiring way too late. We were heads down focused exclusively on building and supporting our product for the first few years. By the time we finally decided to start hiring, we were already in a bad spot. We had too small of a team to support our product’s complexity and all of our customers. We slowed down on our pace of product development. We just didn’t have the team size that the business size and opportunity size justified.

To make matters worse, once we started recruiting/hiring, we didn’t really try hard enough at first. We failed at it for too long because we didn’t really make it a true priority even after we started.

So, when?

I can’t tell you exactly when to hire, but…

Wait until you know you really need to

  • When your current processes are breaking
  • When you know you can’t accomplish your goals without more people

But then don’t wait any longer

  • Once it’s time, make it a #1 priority
  • You must really commit to hiring.

When we finally did really put in a full effort to grow our engineering team, it started taking 50%+ of my time for many months. You can’t half do it and expect results.

Who to hire

Just one simple rule here: Aim high. Don’t settle.

Aim high

In your early engineers, you should look for:

Senior people. Junior people can be a huge asset longterm but will take years to get there, you probably don’t have time for that.

Leadership potential. You need people you don’t need to “manage”. Your first few engineers should be people who you think you can trust leading major projects and taking on large amounts of responsibilities. They should be people you can envision leading future teams.

They should “wow” you somewhere along the way. Can you learn from this person?

Don’t settle

The temptation is strong. It happens every time… somewhere after about a dozen mediocre interviews you might start thinking you just want anybody decent. Resist! DON’T SETTLE. The management and culture hit will slow you down worse than not getting the help you need for a while. There are really great candidates out there – persevere to find them.

Maybes = No’s. If you’re not sold & excited about a candidate, there’s probably a good reason for it. Trust your gut. You should be excited about candidates to move forward. If you think of them as a “maybe”, that’s a great sign you should reject them.

Hiring the wrong people = VERY BAD. Hiring the wrong person can really hurt you in a way that’s hard to recover from. The wrong hire can quickly poison your culture, frustrate your top performers, and set you down the wrong path.

More about “who”

First few hires…

First hires are the hardest roles to fill.

It’s hard to attract the very first few people. Early stage startups barely look like a real job to people. Most people don’t “get it” until there are already other people in the role.

Story: I’ve literally had a fresh out of college job candidate reject our offer (that he was excited about) because his mother didn’t think it was a “real job”.

Most people also aren’t really a good fit for a really early stage startup. Tiny startups just aren’t for everyone. Most people need more structure and a more narrow job description than is typical for a very early startup employee.

First hires are the most important.

They’ll impact the caliber of your future hires. Your first engineers will help hire and attract the next engineers.

They will (or should) become leaders. It’s likely a bad sign for your first few hires if you cannot picture that person leading a project or team one day. Early in startups, you want people who can learn and grow quickly as your startup grows.

Find your bottlenecks

Different stages of your company require different types of people.

The first few engineers on our team were “Full Stack”. As a tiny team we needed engineers who could go both deep and wide. We were able to go deep in specific areas as needed, but also comfortable working across various parts of the tech stack.

Hiring generalists worked well when the engineering team was very small, but as we grew it was time to start specializing. That doesn’t mean restricting people to one part of the tech stack, but rather it meant finding our software development bottlenecks and really focusing on finding some great people in the specific areas we were weakest in.

At, our first specific bottleneck was on the infrastructure side. We had grown with so many customers, so much data, and a larger codebase to support that our engineering team was really slowing down trying to work on both the infrastructure and the product itself. We eventually hired a couple really great DevOps/Infrastructure Engineers, and that dramatically helped.

Next our bottleneck was Frontend Development (couldn’t keep up with Backend), then Design (we finally had frontend bandwidth to implement design work), then Backend, and most recently a Product Manager (for the first time it felt like customer problem discovery/definition was bottlenecking engineering effectiveness).

What’s your biggest team bottleneck for building a better product? Focus your hiring there!

Mistake: Focusing only on technical qualifications

A mistake that I’ve made a couple times is focusing almost entirely on a candidate’s technical capabilities. It’s so hard to find programmers that actually know how to program, that when you find someone who passes your technical quality bar, the temptation is to assume they would be a great hire.

But in reality, it’s incredibly important to pay attention to their written and verbal communication skills, maturity, attitude, desired work/life balance, culture, how they will work with people, their work processes, and other “soft skills” that separates a senior professional from just somebody who is good at programming. Do they have experience working in companies your size? If you’re remote, have they ever worked remotely before? Ask yourself if you actually think you would like working with this person. Finally, always trust your gut.

If you aren’t careful about qualifying candidates as an entire person and not just based on their technical skills, you risk ruining your culture, building a team that is difficult to manage, doesn’t work well together, and who you can’t trust with important projects that aren’t 100% about the code.

How to hire

The 6 steps for hiring are pretty obvious, so I’m going to just provide a few specific tips or hard lessons learned for each one.

  1. Prep work
  2. Sourcing
  3. Screening
  4. Interviews
  5. Offer
  6. Onboarding (& beyond)

How to stand out

Before we get into each step of how to hire, let me first say a couple words about standing out from other companies. Every other tech company is doing these same exact steps. Great candidates have hundreds of great jobs to consider. So it’s important to figure out how to make your company and role stand out among the rest.

Stand out by making the whole process fun and respectful, and play to your strengths.

Be respectful

The best way to stand out is to just treat all your candidates with respect.

Get back to people quickly. People really appreciate if you always get back to them within a few days. Most companies don’t. Setup email templates to make this easy.

Declined candidates should still walk away impressed. Your goal should be to make everyone who interacts with your company to be impressed by you and want to tell their friends, even if they are a horrible fit themselves.

Make it fun

Try to find a way to make the application and interview process fun for candidates. This could look different at different companies, but a couple quick ideas…

Make your interview questions connected in some way so the candidate feels like they are making progress, rather than just being asked a series of unrelated technical questions.

Examples: For one position at we have a 1 hour interview where we ask 12 connected questions that lead a candidate through architecting a real-world standalone web service. For another position, we have an application question that is related to the take-home challenge they’ll do in round 2, and our final interview is based on their work done in the take-home challenge.

Help them learn about your company along the way. The more you can share about your own company and the type of real-world challenges you experience, the more a candidate will understand if the role is a good fit for them (and hopefully be excited).

Example: All our job applications require a candidate to send a POST with a specific JSON payload to an endpoint. The response payload returns not only the “answer” they need to apply, but also returns the URLs to a few engineering blog posts and open source projects of ours so they can learn more about us and our work.

Play to your strengths

There is something unique and attractive about your company or the specific role you’re hiring for. Something that can help you stand out as more interesting than the countless other jobs out there.

Here are some examples of possible strengths:

  • Unique tech stack (niche programming language, big data, using cutting edge frameworks, etc.)
  • New technology area (e.g. virtual reality, cryptocurrency, AI)
  • High salary, significant equity, or unusually strong benefits, etc.
  • Building a new product from scratch
  • Employee #1 who will work closely with founders and wear many hats
  • Ability to work remotely or set own hours

Whatever it is for you… make sure you focus on sharing that. Advertise it clearly in your job description, mention it in your interviews, repeat it when you make a job offer, etc. Play to your strengths.

For us at, we have several advantages over most companies: remote (work from anywhere, no set hours), good tech stack, and profitable. Sometimes we try to even fit these advantages into the job titles – e.g. we have advertised job posts with titles like “JavaScript/React Frontend Engineer @ a *profitable* fast-growing startup” which stands out among a sea of plain “Software Engineer” posts.

Outline of Hiring Plan for one position

Prep Work

Plan your process

Who are you really looking for? Make sure both you and the rest of your team have clear alignment on exactly what type of person you’re looking for.

Plan the entire hiring process upfront. Once interviews start it will be much harder if you’re winging it.

For the last position that I hired for, before talking to a single candidate or even posting a job advertisement, I first wrote a document with the outline shown here (to the right) and made sure the rest of the team was on board with not only who we were looking for, but why now and how we would evaluate that person.

Write the job description

Take your time writing an excellent job post. It’s a mistake to just list out a bunch of requirements. Instead, be sure to:

Sell your company & the role. Remember that great candidates have lots of options – let them know why they should want to join your company.

Be specific about what they’ll do. People don’t just want to know they’ll be writing Python backend code, they’ll want to know specifically what types of projects they will be doing and what their impact will be.

Get feedback from people who are good at that role. After writing your job post, send it around to people who you think are excellent at that role and ask for feedback before promoting it. This is especially important when hiring for a role that you yourself wouldn’t be qualified to fill.


Where do you find candidates? 7 main places…

Outbound vs Inbound. You need to do both. But you have to treat outbound differently (more casual & more selling) in early stages of the process.

Your Network. Your direct and 2nd degree network is already full of great people.

  • Directly reach out to people you think are great – even/especially if you think they are happy in their current situation. Often they are more ready for a change than you may think. And worst case, they might be able to recommend somebody else great for the role.
  • Get everyone in your company to do the same. When you hire someone new, one of the first things you should ask them is who else they know that might be a good fit for your open roles.
  • Use social media. Not as a replacement for reaching out to people directly, but in addition.

A good example of how effective networking hiring can be is how we found our recent Product Manager hire, Ben. I posted the tweet below, and Ben was one of the people who “liked” the tweet. I didn’t directly know Ben, but he had “Product @ [startup]” in his Twitter Bio and had previously cofounded a company with a former colleague of mine. Since he liked my post, I sent him a Direct Message asking him if he knew anyone who might be good for our PM role. Soon enough we setup a call to discuss the role for himself! Not long after, Ben became our first PM and has been doing great.

The most important part: Ben said that if I hadn’t sent him that message, he probably would have never applied!

Job Boards. This is the easiest way to find candidates, since you basically just pay ~$300 and you get your ad in front of a lot of people who are looking for a job. The trick is to post on the most relevant job boards for your specific role as possible. I suggest avoiding the very big name sites like Monster, and instead focusing on specific niche sites that have the most overlap with your ideal candidate as possible.

For example, since our roles are remote, we post on remote-focused job boards like,, and For Frontend Development roles, we post on sites like Codepen. For Designers, Dribbble. You get the idea – the more targeted, the better.

Targeted Communities. Similar to job boards, there are online (and even offline) communities where your target audience hangs out that often you can informally share a job post with. This includes Slack groups, tech newsletters, MeetUps, etc. Often the organizer of these communities are happy to share highly targeted job opportunities with their members.

When looking for targeted communities, consider: industry-specific, location-specific, or technology-specific.

Matching Sites. There are some sites that serve as middle ground between inbound & outbound recruiting. Sites like Hired, AngelList, AList, or TripleByte serve as a place to find profiles of developers who are job seeking (and sometimes pre-screened) where you can browse for developers who look like a really good fit for you and reach out.

Targeted Outbound. Good old-fashioned cold emails to people who look like they would be the perfect fit for your role based on their exact experience. This is a lot of work but can be more effective than you may think.

Tip: If you have open source projects that strangers have made helpful contributions to, reach out to them! If not, then reach out to contributors for open source projects for the libraries that you use.

Recruiters. If you go this route, just be very careful about how they are representing your company.

Example of relentless followup yielding fruit

Relentless follow-up

When you find someone you really want to work with… never stop following up. Recruiting is like sales.

The screenshot to the right shows an example of how I followed up with Casey, a developer that I had met a couple years prior and was really impressed with. You can see how every few months for ~19 months I kept checking in with him. At the bottom, you can see it eventually paid off. Casey is now a really key person on our engineering team.

For two more great examples of this, check out Steli’s blog post on how he recruited me for 3 years before I finally joined him.

If there’s a single lesson here, it’s this: When you find amazing people, follow up and follow through with them forever.


Use an Applicant Tracking System. A dedicated ATS like,, or will make collecting, screening, and tracking a large number of resumes/applicants much more efficiently than just using your inbox/Trello/spreadsheet.

Customize your application form. When advertising jobs online (especially if you’re open to remote candidates), one of the toughest parts is actually having too many applicants, where many of them are low quality. A good solution to this is to customize your job application form to make it fun for the right kind of person and turn away people who just want to blindly apply to as many jobs as possible.

The form should, of course, give them a chance to share their resume/experience, which you’ll pay close attention to when screening.

But a good application form will also include technical screener questions which should only take a few minutes for someone good to solve, but will quickly weed out those who aren’t qualified. We’ve done this and have actually gotten great feedback about it. People love little technical challenges in application since it makes it less boring to apply.

Finally, include questions to help you evaluate their written communication, which is often under-appreciated in engineering hiring.

Having thoughtful questions on your application form will give you way more data when screening candidates.

Customized application form with screener questions


Example flow:

  • Phone Screen (30-60m) – Intro, quick technical screener questions, hear about their experience and determine whether they seem overall impressive.
    • Get good at phone screens – this is where most of your recruiting time will be spent.
    • Have good technical screener questions that get you to a “No” as quickly as possible if someone isn’t great. For me I like to ask something one level deeper than frameworks might take care of for you, e.g. asking about SQL escaping/injection (for people used to ORMs), or direct DOM manipulation (for people used to React).
    • Use for quick “can they actually code” live coding exercises of something simple.
  • Technical Interview 1 (60m) – In depth technical/architectural questions
  • Take-home project (2-3h) – Let’s see them actually build something (2-3 hrs). It’s important to timebox these for candidates so they don’t feel taken advantage of. Some companies actually pay. If you aren’t paying them, then don’t give a real problem that could be misinterpreted as trying to get free work from them.
  • Technical Interview 2 (60m) – Drill in on missing technical areas, evaluate softer skills, overall seniority, etc.

Things to keep in mind during interviews:

The best interviews should feel like a dialog rather than just you asking a bunch of technical questions in a row. For example: Let them tell you about their experience, and ask questions along the way to go deeper.

Remember to sell. Use every interview as a chance to share what you think is great about your company and this role, since great candidates can have many opportunities to choose from.

Let them ask good questions. They should have good ones and be engaged. No questions is a bad sign.

Gut check / do you like them? If something doesn’t feel right during an intro call (when everyone is trying to put their best foot forward) that’s usually a sign of a bad fit.

Have all interviewers leave detailed notes & a summary/conclusion. It’s best if each interviewer independently writes their thoughts immediately following interviews to avoid groupthink. Press interviewers to summarize with a simple rating scale. For example we use language like “strong like” or “weak dislike”. This allows you to review everyone’s feedback afterwards and reject anyone where most interviewers are only ambivalent toward a candidate.


So you found someone you like? Great! Now comes the pivotal offer stage.

Always check references. Before you make the offer, reference checks are key to validate that someone is as good as you think they are, and not just good at interviewing. Great people with a decade or more of experience should easily be able to come up with a long list of names of current or past coworkers that would happily vouch for them.

Cautionary tale 1: Early in’s history we made a hire that turned out to be really, really terrible. In hindsight, I’m sure we would have avoided this hire if we had done thorough reference checks. Sadly, we also saw him go on to get hired (and also eventually fired) by another startup we respected. Had they talked to us first, they could have also avoided wasting time.

Cautionary tale 2: We were once trying to move quickly with a candidate, so we made an offer first but contingent upon reference checks. It turns out that once we did references, we detected that there was a significant error on his resume about the duration of time he spent in the role that was most relevant to us.

Make the offer & close it. Remember to focus the offer not only on the salary & benefits, but especially on selling the opportunity, quality of the team, and their ability for learning/growth.

Trial periods. If you have the opportunity to do a contract / trial period with someone before jumping to a full-time offer, that can be a really great lower risk way to see if they are truly a good fit (and if you are a good fit for them). Not all candidates can risk leaving a good job for a 1 month contract, but when possible, this can be a very useful tool.

Mistake: Assuming that accepted offer = position filled

It’s a rookie mistake to assume that just because a candidate accepts your job offer, that you’ve actually finished your job hiring.

Recruiting doesn’t stop at signed offer. Believe it or not, I’ve had more than one example where a candidate accepts an offer (including signing paperwork) and then changes their mind before their start date. I’ve heard reasons like “I got more excited by a different company”, “my current employer made a counteroffer too good to refuse”, and “my spouse ended up not being okay with the retreat travel requirements”. Don’t get mad, just move on.

You’ll have to make more offers than you think. Between some offers being declined, and some candidates dropping out before their start date, you will end up needing to make more offers than positions you’re trying to fill. While you definitely shouldn’t make more offers than roles you’re prepared to hire for, I do recommend that you continue talking to candidates even after you’ve made an offer, until you have somebody on board doing a great job.

Sell to start date. Keep candidates engaged from offer to start date by checking in along the way and continuing to sell. Specifically, we make sure that in the days/weeks between offer and start date that we send candidates at least a couple emails with links to blog posts we wrote, links to 5 star reviews of our product left by customers, a keynote video from our CEO, etc.

Onboarding & Beyond

Mistake: Assuming that your hires will work out

For the candidates that do make it to their start date, unfortunately many will still not work out – probably a higher percentage than you may think. Either you or they will determine the role isn’t right for them.

Your work doesn’t stop at their start date – focus on post-hire success. The first 1-2 months of onboarding is critical!

Don’t assume new hires are happy. Be checking in with new hires frequently. Ask questions like “We’re a couple weeks in and you’ve gotten a better sense for us now — what’s different than what you expected, good and bad?” New hires just may realize the job isn’t for them. For example, we’ve had someone tell us “I learned more in the past 3 months than the 3 years before that – but the pace is not for me”. Do what you can to make people happy, but then move on if it’s the wrong fit.

Learn from each bad hire. Your goal from each bad hire is to learn how you can improve your hiring process to avoid similar mistakes in the future.

Make sure you’re still excited after 30 days (or fire fast). Realistically, you’ll never get a perfect sense of someone from a few hours of interviews. So after you’ve had some time actually working with someone, if you’re not still excited about them being on the team, fire fast so you both can move on.

Hire more people than you think you need. Realizing that many hires don’t work out, you should plan to hire more people than you actually need in order to end up with the people you really need. Of course, you need to be able to afford everyone that you hire. But where you have room to hire an extra person (e.g. hiring 3 instead of 2 people for a role), doing so will increase your odds of success. It’s much easier to hire in parallel than serial for the same position, so it’s also more efficient to over-hire upfront than to keep restarting your hiring process after a hire doesn’t work out.

Onboarding tips

  • Have a well thought out onboarding plan. I create a Dropbox Paper doc with a long list of checkboxes with a combination of things that they should do (project work) and learn (reading, talking to specific people, etc.) broken down by day and week for the first month.
  • Ship on day 1. Get new engineers to ship something (even if super tiny) on their first day if at all possible. Definitely within the first week.
  • Start with small quick wins but rapidly give them increasingly difficult projects. Get them in the habit of moving fast and shipping, quickly working toward giving them big amounts of responsibility.
  • Spend a lot of time with them. Check in multiple times per week. If hiring remotely, try to find a way to start out in-person if possible.

Managing/running a growing team

Eventually all your hard work hiring will pay off and you’ll start to have a larger team. Now what? Leading a growing team is where things get fun. This topic really deserves its own blog post, but here are a few quick principles that have helped our team scale…

Hand over responsibilities. The sooner you trust your team with big problems and stop trying to control everything yourself, the better.

Have frequent 1:1s. Give the people you manage a meeting every week or two that is primarily their time. Discuss challenges and how they are feeling about work. Ask questions and listen. Tip: I like to keep an Apple Note per person on my team and when I think about something to discuss with them (a question or positive or negative feedback) throughout the week I’ll jot it there, and then use that Note before/after the 1:1 to take notes.

Build camaraderie. Especially with a remote team, it’s important that everyone feels connected and like they are a part of building something great together. Here are a few things that have helped us accomplish that:

  • Retreats. 2-3 times per year we get everyone physically together in one place for about a week (different places around the world). This is where the most important team bonding, vision setting for the year, etc. occurs.
  • Retrospectives. We try to (but need to do them more often!) have open discussions after projects wrap up where we discuss how it went, what we learned, what we did well, and what we’d like to do better as a team next time. The goal is to have a culture of continually seeking growth and improvement through healthy/safe discussions.
  • Show & Tells. When you’re tiny, everyone knows what everyone else is working on. After you grow beyond a few people that’s no longer possible, so having a regular time where people are encouraged to share details of what they’ve been learning/coding/designing/launched/etc. is a fun way to keep people on the same page and decrease bus factor.
  • Highs & Lows. Recently we’ve introduced a time where each person on the team very quickly (~30s/person) shares their High (best part) and Low (worst part) of their past week – related to either their work or personal life. Especially for our remote team, this has been great for hearing about wins and getting a better sense of what everyone is struggling with.

Add structure as the team grows. Setup Project Leads, Tech Leads, Engineering Managers, etc. so that you can give people on your team more responsibility and ensure you don’t bottleneck too many things.

Remote hiring

Being a distributed / all-remote team has been a huge asset for us both in terms of recruiting as well as retention.

Really great people are everywhere. We’ve found amazing candidates from countries all around the world. The Product/Engineering team is 14 people – all remote – working from 6 different countries.

Way more candidates. We pretty quickly get hundreds of candidates for every remote position we advertise. There are just a lot of people in the world who want to work remotely. (This is a blessing and a curse, see the “Screening” section.)

You can really stand out as a top company. It was very hard to stand out in Silicon Valley among all the other tech startups hiring. But among remote-friendly companies we were easily able to stand out as a very attractive option for candidates.

Cheaper. SFBay area engineering salaries, housing, cost of living, and office space are all insanely expensive. Go remote and hire more great people!

Retain employees longer. People stick around longer, since they can live where they want, move around as needed, and don’t have to deal with commutes or distraction-prone offices. At, once we went remote, me and 3 other people on our team moved out of California when family or other needs arose. Our jobs stayed the same even as our locations changed drastically.

For me, working from home has been wonderful as I became a father last year.


Building and growing an engineering team is hard… but it does get easier. And seeing your new team be able to accomplish more than you could ever before is a really great feeling.

Do you have your own tips for growing an engineering team? Questions or want to hear more? Tweet at me.

My InsideSalesSummit interview: How sales and engineering teams should work together

I did an interview for the Inside Sales Summit, a free 5-day virtual summit with perspectives from 50+ leaders on inside sales. Ryan Robinson interviewed me for an engineer’s perspective on topics related to sales, including how sales teams and engineering teams can work best together.

Watch the interview here:


In this interview, Phil digs into his personal experience to share what he believes are the best ways for sales and engineering teams to work together, including how to pitch (in-demand) feature requests to product and engineering and how reps should address feature requests from high-value prospects. Phil also explains why technical founders need to care about sales, and goes over common misconceptions about both engineers & salespeople.

Interview Length: 19:14

Topics Covered: The best ways for sales and engineering teams to work together, how to pitch (in-demand) feature requests to product and engineering, how to address feature requests from high-value prospects, why technical founders need to care about sales, and common misconceptions about engineers & salespeople.

How to log Feature Requests

Below is an internal team post I wrote to help us at do a better job of capturing customer’s feature requests in a way that leads to better product development outcomes.

I’m sharing this publicly because I believe this advice can help other startups and SaaS product companies as well.

When a customer request for a new feature come in, it’s easy to not log it at all (“we’ve heard that one before”) or to log it in a suboptimal way. Here are a few details on how to best capture customer needs and why it matters.

The typical, but unhelpful, way

The most common way feature requests are logged is in a “light” format like this:

  • “ wants feature FOO – [link to ticket]”

While this does provide a list of people to notify if we ever do launch a feature that we happen to call “FOO”, it falls short in many other ways.

This is typically very unhelpful for Product Development or for getting a customer’s needs solved because:

  • Many people have different ideas about what this feature specifically could be.
  • There are different ways a feature could work and this doesn’t give any insight into which way would be best.
  • Frequently there’s a different, better way that their underlying need (use case) could be met instead.

Logging requests in this “light” format may seem at least like a good way to build a notification list, but in reality leads us to building and notifying people about something that turns out not to best solve their core problems.

Similarly, it may seem that this at least gives us a “votes” system to prioritize Product decisions, but in reality there isn’t enough detailed signal in the votes to design a feature we can be confident really helps most of them.

Caveat: Logging a feature request with “email address only” is still better than not logging any at all, because it at least does give us a list of people we can later contact for more information.

The better way

A feature request logging format that is 100 times more helpful is one that includes details of the use case. A use case should include:

  • Who is the user and company, and what’s the user’s role within the company?
  • What situation is causing the user to want this feature? (When did the need start?)
  • In their own words, what do they mean by “feature FOO”?
  • How, specifically, would they use this feature?
  • Really, why do they want this feature? What is the specific use case?
  • What is the business value they’re hoping to achieve because of this feature?

It all really boils down to asking “why?” and then logging in as much detail as possible, in the customer’s own words, the specifics about what they are actually trying to accomplish.

“In the customer’s own words” is really important because it’s easy for us to, in the moment, substitute our own current idea of a solution, when what’s important is logging their actual problem and goals. Having the customer’s own words helps us avoid bias.

How this helps

Demand based Product Development

Having concise but detailed use cases help direct us toward clarity when we try to validate whether a Product Proposal makes sense to move forward with. They help answer:

“Is there really a pattern of consistent use cases that are important enough AND that our specific idea of solution would be a GREAT solution to?”

When logging feature requests, it’s best to assume that, by default, no feature will get developed until there is a set of documented specific use cases with reasons/explanations on why it’s important to that customer and how they would use it.

From a Product Development perspective, it’s important that we pay most attention to the underlying need (demand) rather than customer’s ideas and requests for specific features (supply). (For more about this, there’s a great podcast on the subject).

Design Decisions

Even in times where there’s clear demand in a problem area and many requests for a particular feature, another factor is at play where detailed feature requests with use cases help tremendously.

Often, there are multiple very different ways a feature could work, but without understanding exactly what users are trying to achieve, it’s hard to know which way is better.

Most features which seem clear and straightforward on the surface can go in different directions once you really dig into the nitty gritty UI/UX or technical design. Being able to go back to the core use cases (specific real-world things our customers are trying to do) it helps us quickly and confidently choose a direction.

Who should do this

The Product team takes ultimate responsibility for fleshing out customer use cases and validating solutions with customers.

Anyone who talks with customers, however (especially Support, Success, and Sales), is in a unique position to already have many interactions where feature requests and customer problems come up. It is immensely helpful and valuable for moving the product forward in the right ways if these interactions result in feature requests logged with use cases. We love having the entire company championing customer needs and giving input into Product direction. This is the best way to do that.

At, the best place to log feature requests with uses cases is in our “Feature Requests & Customer Needs” Trello Board. If you’re ever inclined to write a Product Proposal, including a few curated use cases (in the customer’s own words) is useful to supporting the idea.

What do you think? Send me a tweet about how your team captures customer feature requests.

Don’t punish old trials and former customers

A common pattern in SaaS apps is to allow a free trial period of 2 weeks or 1 month, and then to require a credit card to use it any longer.

Either out of curiosity or out of a genuine need for a tool that some SaaS service is offering, I will often sign up for a free trial soon after learning about it in order to check it out. For a variety of reasons by the time the free trial is up, I’m not ready to purchase.

It could be because I was just poking around. But more often it’s because I got too busy. Or my reason for signing up didn’t stay a high enough priority to be ready to purchase and fully implement some solution. Or maybe because the product just wasn’t far enough developed to satisfy what I was looking for.

What I find happening is that 3, 6, 12, or 18 months later I’ll find myself thinking about this tool. Perhaps the problem that led me to originally check out tools of the service has become more pressing than ever before. Or perhaps I’m fed up with another tool I chose, and am searching again for a better option. Or I’m hoping that the product has evolved more. Or whatever.

When logging back into your previously-created account, what you typically see is something like this:

Your free trial expired. Please enter your credit card to continue.

At this point, it’s far too easy to just close the tab. I’ve done it many times, even when I actually was in need (and willing to purchase) a tool in their category.

Savvy users will email the site’s sales or support team and can usually get a trial extended, but this often takes a few hours, which sucks. When a user gives you enough attention to want to check out your product right now, you should always take advantage of that. It’s too easy for that attention to get lost if you make the user wait.

Similarly some users will just use another email address, which is really bad for understanding your marketing funnel and metrics, and is a bad user experience overall. Plus, this may mean losing whatever progress was made on the first trial.

Let’s stop punishing our older trials.

I always thought this was a bad user experience, but I knew we were guilty of doing the same thing at Not anymore. Now if you login to a trial that has been expired for long enough (i.e. you haven’t checked out the product in a long time), we give you a single click to get started again.

Screenshot 2015-08-04 14.50.22

The same applies for former customers. Once you’ve been around a while, it won’t be uncommon for an early adopter to have churned and then want to give you another shot a year later. We should welcome this!

It’s a super easy change to make and within minutes of pushing this change we already saw its effects pushed to our chat.

Screenshot 2015-08-07 16.53.17

Don’t treat old users worse than new trials!

Comments (10)

Why you should Archive your emails when you’re done with them

There are two types of people in this world:

  1. People who archive incoming emails when they are finished with them, and try to achieve “inbox zero”
  2. People who leave all incoming email in their “inbox” forever but use “Mark as Unread” or Star/Flag message to indicate items still requiring action

If you’re a Type #1 person, you can stop reading now.

If are a “never archive anything” type person, I’d like to make a case for why you should become an “inbox zero” type person and archive emails when you’re done with them.

Read the rest of this entry »

HackToStart Podcast

I got interviewed for my first podcast recently. It’s called HackToStart, and it’s one that I’ve been listening to for a while now, so I was excited to be a guest on the show. Check it out here:

Hack To Start | Phil Freo, Full Stack Developer,

In the show we also discuss one of my latest blog posts: The last 20% before shipping.

The last 20% before shipping

What makes a new feature or product update “done” versus what makes it “really done”? At we developed our own process to answer this question, based on years of shipping new features for our sales communication platform. Today, we want to share this checklist with you.

As soon as a new feature you’ve built is running and working on your development server, there’s a strong temptation to think it’s “done”. You want to ship it. After all, the code is working, and you know many of your users would benefit immediately from the change.

It’s important to stop and ask yourself: What else should we do other than just making a new feature functional? How can I improve the experience for users or make this feature more accessible and maintainable?

We’ve learned that when you’ve only gotten a feature working, you are at most “80% done” and that the last 20% really makes all the difference. In fact, sometimes the “last 20%” of polishing a feature can take just as long to get right as the “first 80%”, however it also is what separates good from great.

Here’s our internal checklist that we use before launching significant new features or changes. Whether for launching a new reporting feature or migrating our database to a different cluster, we’ve found this list to be really helpful.


  • Is it fast?
  • Are the database queries optimized?
  • Will it scale for a large number of records?

Code quality

  • Is the code cleaned up, organized, and properly abstracted?
  • Is it well commented / documented? Assume someone else will have to maintain it.
  • Are there relevant unit tests?

Edge cases

  • Do you consider and properly handle edge cases and invalid inputs?
  • Did you test the first time experience / no-data-yet case?
  • Is it localized? Consider timezones for anything date specific, and unicode.
  • Did you test in all the supported browsers / platforms?
  • Have you tested for potential security vulnerabilities?


  • Does the UI look polished? Is it consistent or better than the rest of your application?
  • Does the UI react to various edge cases properly?
  • Is any written text as good as it can be? Did you check for typos?


  • Does it need any special deployment process, and is the process well documented? Any data or schema migrations necessary?
  • Is everything backwards compatible? If not, did you communicate the change to users?
  • Should it be deployed/visible to employees only to dogfood/test for a while?


  • Are the relevant API docs complete?
  • Do any FAQ docs need to be updated or written?
  • Did you write a blog post announcement, if appropriate?

There you have it. Run through this list before shipping something new and you’re guaranteed to have a better launch.

Do you have anything to add to the list? Let me know!

Solve multiple problems at once

Startup engineering teams face many decisions about what to build. At, many areas compete for the focus of our small engineering team. Customers often have one little thing they really need. Our team envisions the next big thing to move the product forward. There are poor UX workflows to optimize. We have an idea on how to grow our customer base faster. And of course there are always bugs to fix. The list is endless!

A small engineering team doesn’t have the time or resources to regularly improve every part of a product. It’s not uncommon for a section of an app, once launched, to remain untouched for a year or longer. We usually work to solve a problem or empower customers in a new way or fix a pain point, and then we move on to something else.

One reason I believe our super small team at has been successful is that we often solve multiple problems at once. When a feature needs to be built, we often expand the scope a bit to include other related problems or features that naturally go together with the first one.

Another way to phrase this idea is: rather than solving a problem, solve an entire class of problems.

While this advice may sound obvious, there’s enormous pressure to finish a project as quickly as possible. There’s always the next important feature, bug fix, or redesign from the roadmap to move on to. Shipping even the smallest version of a feature on time can be difficult enough already, since software schedule estimating is never easy. But there’s great value in not just shipping a feature or fix in the smallest form possible.

Application: Fixing Bugs

Let’s start with a simple real-world application: you notice a bug that needs fixing. After some investigation you figure out what the problem is and how to fix it. You fix it and maybe even add a unit test for this case. Time to move on, right?

NO! If you stop there, you’re making a crucial mistake.

At a minimum, you should try to figure out if the same bug exists anywhere else in the codebase. Often a single ack search is enough to find the same bug in many places. Next, consider if there might be other conceptually similar versions of this bug elsewhere. Ideally you’d also follow The Five Whys and discover how this bug got introduced, how it got past code review and QA, etc.

Again, this advice may seem obvious, but consider someone’s natural instinct when a user complains. These complaints usually come in the form of a vague problem (e.g. “it won’t let me change my email address”). First you figure out what the real bug is (e.g. “The form for changing email addresses doesn’t show a confirmation message”). Bugs usually come in very specific forms like this. It’s not uncommon for a programmer to simply fix the bug and move on. But it’s important to stop and consider if similar bugs may exist elsewhere (e.g. “how form confirmation messages work throughout every part of the app”).

It’s the sign of a mature programmer to ask “why” and consider preventing future bugs of a similar type.

Example: A broken URL on

I recently noticed a problem with a specific URL on our site was not working. I discovered the cause was that two Python view functions in our Flask app shared the same name, which just silently breaks one of them. My first instinct was to rename the broken view with a unique name, and move on.

But I remembered it wasn’t the first time this had happened and I recognized it likely wouldn’t be the last, so I thought about the problem more broadly. I knew a syntax issue like this should be detectable, so I spent some time setting up pylint and reviewing its results. Pylint uncovered another case of the same error, as well as other types of logical errors elsewhere, which I fixed. Finally, I added pylint to our continuous integration system to automatically detect any Python syntax issues in the future.

So rather than fixing the broken URL, I fixed all cases where URLs were broken for the same reason. I also found and fixed other unrelated instances of “detectable” syntax issues. And I also automated this process so that this entire class of issues can never happen again. Do you see how much more powerful this type of fixing can be?

Once you’ve discovered the specific causes of a bug, there’s no better time to find and fix other similar bugs. Even when the roadmap begs you to move on, the benefits of squashing related problems are even stronger:

  1. It’s good practice to fix bugs before writing other code (#5 in The Joel Test)
  2. If you can discover and fix bugs before more users experience and report them, you’re preventing user pain.
  3. You’ve already done the hard part of figuring out the specifics of the problem. If you don’t completely resolve it, you’re forcing a teammate or your future self to have to waste time relearning the same thing!

Don’t just fix bugs. Fix an entire class of bugs.

Application: Designing Features

The temptation to solve a single problem at once is even larger when it comes to features. Features are often a response for solving a user’s pain point, or an idea designed to empower your users in a new way. Naturally, the team is excited to ship as soon as possible.

Furthermore, a good product designer will optimize a feature to be as simple as possible for the specific workflow it’s designed for.

The problem is that over time, rather than designing one cohesive experience, you’ve glued a bunch of individual features together. If you’re only thinking about solving one specific problem at a time, you’re missing the bigger picture of how everything will fit together.

Software written in this way turns out to be super complex because hundreds of small problems were solved separately rather than a few big problems being solved elegantly.

Don’t design a single feature; always be designing for the bigger picture.

Example 1: Search & Filtering

An example where I think our team nailed this early was with search and filtering. From ElasticSales we knew that salespeople would want to slice and dice their leads in a million ways.

When starting it would have been understandable if we solved this initially by just slapping a couple of the most commonly requested filter options, like “Lead Status”.

However we knew that this was a narrow and short-term solution. It wouldn’t be enough to last and wouldn’t be enough to “wow” people. Quickly, power users would outgrow our simple filters and we would be forced to keep adding additional one-off filters and complexity. We’d have to keep redesigning as the number of filters grew and redesigning again for each new idea like exclusion filters or nested “OR” conditions. We would have started fast but slowed very quickly.

Instead, we designed a framework to solve the larger problem. We invented a search language and then UI to allow filtering by a very large number of useful sales attributes and combine them together with boolean and/or/not keywords. It took longer to do it this way than just adding a couple basic filters. But we established a paradigm of how searching and filtering worked in that has powered innumerable use cases our customers needed and has lasted 2+ years. Our customers rave about its power, and PandoDaily wrote about it.

I’m definitely not saying we had to build this feature to 100% completion from day 1 (and we didn’t – we still iterate on it today – and in many ways it’s very far from complete). But thinking through a scalable solution for this problem rather than slapping on a few quick filters has given us a big advantage. Having an end goal in mind allowed us build a version 1 that didn’t have to be thrown away when we built v2 and v3. We have ideas for what an amazing version 5 and 10 may look like, and we won’t have to start over – all because we planned ahead to solve search & filtering more broadly.

Example 2: Reporting

Some of our competitors have dozens of individual “reports”. They tack on a new report every few weeks because users always want more reporting. was really far behind in reporting but the thought of adding dozens of reports made us want to cry. So instead we built one super powerful charting tool (Explorer) that, in one fell swoop, allows you to visualize almost any attribute of your teams’s sales activity.

Example 3: Bulk Actions

We needed to build a way for users to “bulk delete” all their leads. Rather than building this alone, we designed a system that would work for not only Bulk Delete but also Bulk Edit and Bulk Email (two other features we knew we wanted to build). Because of designing for this, we were able to later launch the additional two features within a very short period of time. Coding the two additional features became much simpler and the UX for all bulk actions was considered together rather than tacked on without cohesion.

Architect your product to solve an entire class of problems at once.

If you don’t, you’ll end up with software that’s missing important features and users will quickly outgrow the one thing you helped them with. Or you’ll keep tacking on additions in a non-cohesive way which makes a complex product over time.

Said another way: it’s easier to end up with both successful users and a cohesive UI & UX if you solve and design for a few big problems rather than a bunch of individual little ones.

Application: Refactors

Technical debt can clearly become a big problem and slow development. But it almost never feels worth rewriting something just for the sake of code quality. The benefit of doing projects solely to “pay back” technical debt is hard to justify.

The best time to solve technical debt, refactor code, etc. is in the midst of making other changes to that part of the system. When you’re working on an improvement involving a problematic part of the codebase and you’re considering making bad code even worse… go ahead and take the extra time to refactor and improve it. There’s no better time to do so, since you’re already having to grok how it works and carefully test those parts related to your improvement.

Application: Redesigns

When redesigning how one part of your product works, consider how the rest of your product works. It may be easier to solve multiple problems that relate to each other all at once.

Example: Onboarding Process & Email Setup

We wanted to introduce a set of onboarding steps for new users. One step would be an easier way to connect your email account (for our 2-way email syncing to work) rather than having users do so later in Settings. What we did is build and launch a few features all at once:

  • Onboarding steps for new users
  • Simplify getting email account credentials by:
    • Auto-detecting your email service, IMAP/SMTP hostname, port, etc. when possible
    • Consolidating setup of incoming & outgoing email settings into one step
    • Use OAuth instead of passwords when a Gmail / Google Apps account is detected
  • Support for multiple email accounts & identities per user

Not all of these features were crucial for the main priority at the time, which was to improve onboarding and make it easier to setup email. But they made a lot of sense to build together, since they were interrelated. We would have had to redesign, recode, and retest the Email Settings page regardless so it was the perfect time to design it to support setting up multiple accounts.

Supporting multiple accounts is a valuable feature that we always planned on building. But if we hadn’t built it alongside these other features, it likely wouldn’t have become a big enough priority to get built on its own for quite some time. By building to solve multiple problems at once, we were able to do more, faster, than had we been trying to solve independent problems in serial.

Risks & Rewards

You may now be thinking, “Isn’t this scope creep, and isn’t scope creep a bad thing?”

Indeed, if you keep expanding the scope of your projects to solve more and more problems you will never ship or meet deadlines.

But I’m actually advocating for more planning. More deliberateness in your design decisions and planning. More hesitation before starting projects that only solve only one problem. Design your product with end goals in mind. Design code and processes with your future team in mind.

Expand a project scope opportunistically where it makes sense. Reschedule items onto the roadmap sooner if they are easier to build alongside whatever your current priority is. Often you won’t return to a problem for many months or even years. So if you can put an entire set of problems to rest all at once, do it even if it takes a bit longer.

The principles I’ve been talking about should help you make a much better product over time. When you solve one problem, it’s not that much harder to solve a bigger class of the problem.

The way to keep from turning this advice into scope creep is to slow down. Not slow down in the sense that your team & product shouldn’t be moving quickly. But slow down in the sense that you should do less, but better. Do fewer things, but more that have longterm impact. You can’t do this for everything, but try to do it for the important parts.

So the next time you design a feature, fix a bug, or otherwise try to improve your product, ask yourself, “Can I solve multiple problems at once?”

Comments (1)

My mobile workstation setup

Since my wife got a nursing job in Stockton, I’ve drastically increased the time I spend working while traveling, in coffee shops or hotels where I don’t have my typical desk and monitor setup. Since I get a ton of compliments and questions about it when I’m working from Starbucks all day, I thought I’d share my ultimate mobile workstation guide.

Read the rest of this entry »

« Previous entries Next Page » Next Page »