<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[FormSwift - Medium]]></title>
        <description><![CDATA[We are a cloud-based service that helps individuals and businesses easily customize, sign, and download popular business, legal, and personal forms. - Medium]]></description>
        <link>https://blog.formswift.com?source=rss----ca109bdff32c---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>FormSwift - Medium</title>
            <link>https://blog.formswift.com?source=rss----ca109bdff32c---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 16 Apr 2026 18:31:03 GMT</lastBuildDate>
        <atom:link href="https://blog.formswift.com/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[We’ve Joined Dropbox!]]></title>
            <link>https://blog.formswift.com/weve-joined-dropbox-9fefbf64beb1?source=rss----ca109bdff32c---4</link>
            <guid isPermaLink="false">https://medium.com/p/9fefbf64beb1</guid>
            <dc:creator><![CDATA[FormSwift]]></dc:creator>
            <pubDate>Fri, 16 Dec 2022 14:06:17 GMT</pubDate>
            <atom:updated>2022-12-16T14:05:39.817Z</atom:updated>
            <content:encoded><![CDATA[<p>We started FormSwift over a decade ago because we wanted to solve a problem that many small businesses face in order to successfully run their companies: the time, energy and money spent filling out seemingly endless amounts of commonly used forms by hand. We wanted a simple solution that would allow people to create, complete, edit and save any form — from employee onboarding waivers, to rental agreements, to NDAs — from start to finish. We saw an opportunity for software to play a meaningful role in solving this problem while also reducing our carbon footprint by helping to make the world paperless.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K5Vnyg9zaz-SM0pqHdbQoQ.png" /></figure><p>We’re proud of what we’ve accomplished: Our platform has been used to create more than ten million documents by small business owners and contractors around the globe. We’ve also built an amazing team and culture.</p><p>Today, we’re excited to share that we’ve been acquired by Dropbox and will be joining the Dropbox team. We share a similar goal to digitize, simplify and modernize work for customers in an increasingly distributed and virtual landscape. The combination of our vast template library with Dropbox’s robust document signing and sharing capabilities will help bring even more customers a solution for all of their agreement workflows. And we share common values: like us, Dropbox values people, and we’re excited to grow together!</p><p>We firmly believe that this is a natural next step in our journey and are thrilled to join forces with a respected, global technology brand. Thank you to all of our users, team members, and friends who have supported us throughout this amazing journey.</p><p>We’re excited for our next chapter!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9fefbf64beb1" width="1" height="1" alt=""><hr><p><a href="https://blog.formswift.com/weve-joined-dropbox-9fefbf64beb1">We’ve Joined Dropbox!</a> was originally published in <a href="https://blog.formswift.com">FormSwift</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Adventures: Adding a Dynamic List of IP Addresses to a Security Group Using Terraform]]></title>
            <link>https://blog.formswift.com/aws-adventures-adding-a-dynamic-list-of-ip-addresses-to-a-security-group-using-terraform-df9233614605?source=rss----ca109bdff32c---4</link>
            <guid isPermaLink="false">https://medium.com/p/df9233614605</guid>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[network-security]]></category>
            <category><![CDATA[terraform]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[travis-ci]]></category>
            <dc:creator><![CDATA[Rich]]></dc:creator>
            <pubDate>Thu, 11 Aug 2022 16:25:58 GMT</pubDate>
            <atom:updated>2022-08-11T16:25:58.332Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hinV3S9e_riGhlFf" /><figcaption>Photo by <a href="https://unsplash.com/@theshubhamdhage?utm_source=medium&amp;utm_medium=referral">Shubham Dhage</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>My current test automation infrastructure requires that I allow traffic from my build server to reach my internal test servers. However my build server has a changing set of IP addresses it uses for its build machines. As a result I periodically get connection timeouts because new build machine IP’s are not in the current security group, while decommissioned build machine IP’s are.</p><p>Initially I updated this list manually and then re-applied Terraform code to update the security group. However I discovered that I could get a list of the build machine IP addresses through an API in JSON format. Additionally Terraform has a built-in HTTP client that can be used to get this list.</p><p>The HTTP data block below sets the URL and headers. The local variable stores the processed IP address list. In this example I need to transform each IP into CIDR format for the AWS Security Group. Then in the security group I can reference the local variable containing the new list. This updates each time <strong>terraform apply</strong> is run.</p><pre>data &quot;http&quot; &quot;build_machine_ips&quot; {<br>  url = &quot;https://dnsjson.com/<strong>DNS_NAME</strong>/A.json&quot;<br><br>  request_headers = {<br>    Accept = &quot;application/json&quot;<br>  }<br>}</pre><pre>locals {</pre><pre>  # Excessive formatting is only for post. the following code<br>  # should all be one line.<br>  <em>build_machine_ip_list</em> = sort([ <br>    for ip in jsondecode(<br>      <strong>data.http.build_machine_ips</strong>.body<br>      )[&quot;results&quot;][&quot;records&quot;]:</pre><pre>      # Reformat list into CIDR notation for AWS<br>      &quot;${ip}/32&quot;<br>  ])<br>}<br><br>resource &quot;aws_security_group&quot; &quot;build_machine_sg&quot; {<br>  name        = &quot;NetworkPorts&quot;<br>  description = &quot;Allow external access&quot;<br>  vpc_id      = &quot;<strong><em>xxxxxxxx</em></strong>&quot; # Add your VPC<br><br>  ingress {<br>    from_port   = 80<br>    to_port     = 80<br>    protocol    = &quot;TCP&quot;<br>    cidr_blocks = local.<strong>build_machine_ip_list</strong><br>    description = &quot;Access to internal servers from build machines&quot;<br>  }<br>}</pre><p>Now it updates automatically when the infrastructure is applied.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=df9233614605" width="1" height="1" alt=""><hr><p><a href="https://blog.formswift.com/aws-adventures-adding-a-dynamic-list-of-ip-addresses-to-a-security-group-using-terraform-df9233614605">AWS Adventures: Adding a Dynamic List of IP Addresses to a Security Group Using Terraform</a> was originally published in <a href="https://blog.formswift.com">FormSwift</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Indefinitely Long-Lived AWS Sessions Using aws-vault and docker-compose: The Guide I Wish I’d Had]]></title>
            <link>https://blog.formswift.com/indefinitely-long-lived-aws-sessions-using-aws-vault-and-docker-compose-the-guide-i-wish-id-had-b1e3166daf46?source=rss----ca109bdff32c---4</link>
            <guid isPermaLink="false">https://medium.com/p/b1e3166daf46</guid>
            <category><![CDATA[aws-vault]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[docker-compose]]></category>
            <category><![CDATA[authorization]]></category>
            <dc:creator><![CDATA[Rob Thomas]]></dc:creator>
            <pubDate>Wed, 06 Apr 2022 00:14:56 GMT</pubDate>
            <atom:updated>2022-04-06T00:14:56.204Z</atom:updated>
            <content:encoded><![CDATA[<h3>Perpetual AWS Authorization Using aws-vault’s Simulated ECS Metadata Server In a Container</h3><h4>Your local development services can be up and authorized indefinitely for AWS. Role chaining? MFA tokens? No problem! Use aws-vault’s ECS Metadata Server and take restarts out of your workflow.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mpv2L1oDr89CRhu1GQcf4g.jpeg" /></figure><p>We use AWS for a variety of things here at FormSwift, including a few that are required for local development. We use <a href="https://github.com/99designs/aws-vault">aws-vault</a> to safely manage individual identity accounts and only grant useful permissions to roles the individual developer assumes.</p><p>Additionally, for the past few months I’ve been working on revising our dev stack from a legacy hodgepodge of Vagrant boxes and separate Docker projects to a pseudo-monorepo using git submodules with one docker-compose.yml to rule them all (look for another article as this effort matures). One of the challenges I’ve faced in this project is that, while the straightforward aws-vault usage stays within a typical developer’s pain tolerance when applied to single services, using it for a whole bunch of services running in a single docker environment magnifies the inconvenience into a pretty poor developer experience.</p><p>In searching for a solution, I found a few thread comments here and there <em>claiming</em> it was possible, but precious little in the way of specifics. After spending a fair amount of time poring over the aws-vault source and chasing a few dead ends, I found an approach that works. I feel I would be remiss in not writing up the guide I couldn’t find a couple of weeks ago.</p><h3>What is aws-vault?</h3><p>The AWS CLI and assorted client libraries typically use environment variables to get their credentials, the most notable being AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. There are a few security concerns implied by the naive application of this pattern. The values are pretty impossible to remember, and annoying to copy/paste for every operation that needs them, which encourages people to do unsafe things like adding AWS credentials to their shell config. Additionally, when you use your own credentials directly, you’re typically providing them to code that is <em>probably</em> fine, but you also probably haven’t audited it for misuse of the credentials.</p><p>The aws-vault tool provides a few features that help to manage your credentials in a secure and consistent way. It achieves this with a few moving pieces:</p><p><strong>Cooperation With AWS CLI:</strong> When you run aws-vault it uses the configuration in your ~/.aws directory to provide the information it needs about your account, roles and the like. You don’t even need the AWS CLI installed for aws-vault to consume its configuration files.</p><p><strong>The Vault</strong>: Your real, persistent AWS credentials are added to a secure store, encrypted and password-protected. These are rarely exposed directly to a process. The vault can be backed by a number of different specific implementations. On a Mac, for instance, the default will be a Keychain. Because your credentials are in the vault, you do not need to add them to your account section in ~/.aws/credentials (and, in fact, should not). When using the AWS CLI by itself, your credentials file would often look like this:</p><pre>[myaccount]<br>aws_access_key_id=AKIA****************<br>aws_secret_access_key=****************************************</pre><p>When using aws-vault your credentials file can be as simple as this:</p><pre>[myaccount]</pre><p>Then instead of just running, say,aws s3 ls..., you will run aws-vault exec myaccount -- aws s3 ls.... Because aws-vault knows your credentials live elsewhere, it will prompt you for your vault password (if necessary) and find them there, then pass credentials on to the AWS CLI process as environment variables.</p><p>You can see what information aws-vault sends along to the consuming process like this:</p><pre>$ aws-vault exec myaccount -- env | grep AWS</pre><p><strong>Temporary Credentials:</strong> By default, aws-vault automates the process of deriving temporary credentials from your real credentials using the GetSessionToken API of the <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html">AWS Security Token Service</a>. These credentials are valid for twelve hours by default, but can be requested with validity ranging from fifteen minutes to 36 hours. It is these temporary session credentials that aws-vault uses for further operations. This reduces the severity of the breach in the unlikely event the consuming process does something bad that leaks the credentials it receives. You typically never see your actual account credentials again.</p><p>If for some reason you need your real credentials (read on to see one), aws-vault provides a way to bypass this automatic generation of temporary credentials using the --no-session flag. Suppose you ran this:</p><pre>$ aws-vault exec --no-session myaccount -- env | grep AWS</pre><p>You would see that the AWS credential environment variables are set to your actual key id and secret key. This, of course, disables some of the security aws-vault affords you, and should not be used without considering other ways of achieving what you want first and auditing what you do with the credentials once you have them.</p><p><strong>Automatic Role Assumption:</strong> Your AWS environment may use roles your account can assume to acquire specific permissions, and aws-vault will read those roles out of your ~/.aws/config file and make them available. As a simplified example, your config file might look like this:</p><pre>[default]<br>region = us-west-1</pre><pre>[profile myaccount]<br>mfa_serial = arn:aws:iam::012345678901:mfa/me</pre><pre>[profile local-developer]<br>source_profile = myaccount<br>role_arn = arn:aws:iam::012345678901:role/LocalDeveloperRole<br>mfa_serial = arn:aws:iam::012345678901:mfa/me</pre><p>When you run aws-vault exec local-developer -- ... the local-developer profile will be dereferenced back through the temporary credentials and ultimately to your real credentials in the vault, so that the credentials the consuming process receives represent you operating under your organization’s LocalDeveloperRole.</p><p>This will also handle prompting for your MFA token as needed. By default aws-vault will prompt at the terminal, but there are other supported ways of providing the token. The --mfa-token (or -t) flag to aws-vault will let you supply the token on the command line. You can also add a credential_process that lets aws-vault prompt you with (among other options) a GUI popup. For example, on a Mac, you can add this line to your credentials or config file sections:</p><pre>credential_process = aws-vault exec identity --json --prompt=osascript</pre><p>This will invoke aws-vault&#39;s prompting mechanism to get the MFA token, which in turn we have configured to use osascript to pop up a Mac input dialog. This is handy for some IDE plugins that can’t prompt at a terminal.</p><h3>Here there be dragons</h3><p>For all the benefits aws-vault provides, it combines with some AWS behaviors to pose some significant problems for keeping a service running with authorization for long periods:</p><ul><li>The temporary credential returned by the GetSessionToken API can have a relatively long lifetime, but is always limited.</li><li>Likewise, the AssumeRole API also returns a time-limited credential.</li><li>Eventually, in a few hours, AWS will want you to provide your MFA token again to prove you can get new credentials.</li><li>Even more restrictively, when you use a temporary credential to assume a role, you get only get one hour to work before your credentials go off. No, your friendly neighborhood devops engineer did <em>not</em> configure this short timeout because they hate you and want you to be sad. AWS considers this situation <em>role-chaining</em> and imposes the hard one-hour limit itself. It cannot be extended.</li></ul><p>You can partly get around the one-hour limit using --no-session, but the other limits still apply, so if you aren’t restarting every hour, you’re at best restarting every other day or so. For someone (like me) who likes to keep a full environment running in the basement to offload resource use from the laptop, even this is a bit of a pebble in the shoe.</p><h3>To infinity and beyond</h3><p>Getting around the time limits here requires a few lesser-known features of aws-vault. There is a partial solution in the contrib/_aws-vault-proxy directory of the aws-vault repo. It’s similar to what I came up with in many ways (like, it probably would have saved me some time if I’d looked in the contrib directory earlier), but uses a little Go utility to do the work.</p><h4>aws-vault as a service</h4><p>There is an option to run aws-vault in a mode that emulates the metadata servers for either EC2 or ECS. In real AWS, the metadata servers are how your application gets information about its runtime environment, including its authorization.</p><p>You can get an EC2-style server with --server or an ECS-style server using --ecs-server. Because I’m working in a Dockerized environment, and because it seemed to offer a little more flexibility, the ECS server seemed the right choice here.</p><p>Once running, the server will dutifully spit out credentials on demand. Instead of providing AWS credentials directly to the application, you give the server URL using the AWS_CONTAINER_CREDENTIALS_FULL_URI environment variable. Many (but not all) AWS client libraries (including <a href="https://boto3.amazonaws.com/v1/documentation/api/latest/index.html">boto3</a> used in our Python services) already understand and respect this environment variable.</p><p>Here, too, there are complications. The ECS server binds to a random free port, and requires a random token in an Authorization header to work, which is provided in the AWS_CONTAINER_AUTHORIZATION_TOKEN variable. If aws-vault is running in a docker-compose service, the environment variables it supplies to its child process are hard to communicate to sibling services.</p><h4>Authorization by proxy</h4><p>To work around the environment variable issues, a reverse proxy was the silver bullet. In my implementation I chose <a href="http://tinyproxy.github.io/">tinyproxy</a> due to its small size and ease of configuration for small-scale applications. The configuration for tinyproxy does not support environment variables, but even given that limitation it was easier to use a script that writes out the configuration and then launches the proxy than to use another solution.</p><p>Here, then, is my proxy.sh:</p><pre>#!/usr/bin/env bash</pre><pre>cat &gt; proxy.conf &lt;&lt;EOF<br>Port 80<br>Listen 0.0.0.0<br>AddHeader &quot;Authorization&quot; &quot;${AWS_CONTAINER_AUTHORIZATION_TOKEN}&quot;<br>ReversePath &quot;/&quot; &quot;${AWS_CONTAINER_CREDENTIALS_FULL_URI}&quot;<br>ReverseBaseURL &quot;http://169.254.170.2/&quot;<br>ReverseOnly Yes<br>EOF</pre><pre>tinyproxy -c proxy.conf -d</pre><p>The service is thus exposed on the container’s HTTP port, which is proxied to aws-vault&#39;s random-port URL, and we inject the correct Authorization header so that other services do not need to know the token.</p><p>Note the hardcoded link-local IP address in the ReverseBaseURL. Although you could give this a domain name within the docker-compose network, boto3 (and maybe others — I haven’t checked) will only connect to metadata servers on 169.254.170.2 or 127.0.0.1, and the IP must be provided as such. A DNS name will be rejected even if it resolves to a supported IP address.</p><p>The authorization token is optional, and the header will only be sent from the client if the environment variable is set. Therefore, to give your other services access, you only need something like this in your docker-compose service definition:</p><pre>environment:<br>  AWS_CONTAINER_CREDENTIALS_FULL_URI: http://169.254.170.2/</pre><p>The client will see the URL variable set and hit the aws-vault service on port 80 to request a set of credentials, and the request will be proxied, with the authorization token added, to the actual random-port server running in the container.</p><h4>You don’t know me</h4><p>Now we’ve got a service to provide credentials, but we still need to get it authorized in the first place.</p><p>For the service to be able to generate a stream of updated credentials, it needs access to your real AWS credentials. One way to do this is by using --no-session:</p><pre>$ aws-vault exec --no-session myaccount -- docker-compose up -d</pre><p>This will supply your persistent AWS credentials to the docker-compose environment, and you will forward the credential variables into the aws-vault service. I supply them as build args because we can do most of the setup at build time and thus avoid having the credentials obviously visible in the container’s environment when you exec in. In docker-compose.yml:</p><pre>services:<br>  aws_vault_server:<br>    build:<br>      context: ./aws_vault_server<br>      args:<br>        - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}<br>        - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}<br>        - AWS_USER=${AWS_USER:-}<br>    container_name: aws_vault_server<br>    networks:<br>      local_addr:<br>        ipv4_address: 169.254.170.2</pre><pre>  my_authorized_service:<br>    ...<br>    environment:<br>      AWS_CONTAINER_CREDENTIALS_FULL_URI: http://169.254.170.2/       <br>    networks:<br>      my_app_network:<br>      local_addr:</pre><pre>...</pre><pre>networks:<br>  my_app_network:<br>  local_addr:<br>    ipam:<br>      driver: default<br>      config:<br>        - subnet: 169.254.170.0/24<br>...</pre><p>Notice how we define a network to serve the link-local address range the service needs to live in, and then explicitly assign the correct IPv4 address to the service in that network. The service that needs credentials will then need to be given the credentials URI as an environment variable, and will need to be attached to the link-local network in addition to the overall application network.</p><p>Then in aws_vault_server/Dockerfile:</p><pre>FROM<em> </em>archlinux<br>ARG<em> </em>AWS_ACCESS_KEY_ID<br>ARG<em> </em>AWS_SECRET_ACCESS_KEY<br>ARG AWS_USER<em><br></em>ENV<em> </em>AWS_VAULT_BACKEND=&quot;file&quot;<br>ENV<em> </em>AWS_VAULT_FILE_DIR=&quot;/root/.aws-vault&quot;<br>ENV<em> </em>AWS_VAULT_FILE_PASSPHRASE=&quot;XXXXXXXXXXXX&quot;<br>COPY<em> </em>files<em>/ /<br></em>RUN<em> </em>AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \<br>    AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \<br>    AWS_USER=${AWS_USER} \<br>    <em>/</em>provision.sh<br>ENTRYPOINT<em> </em>[&quot;aws-vault&quot;, &quot;exec&quot;, &quot;--ecs-server&quot;, &quot;--debug&quot;, &quot;local-developer&quot;, &quot;--&quot;, &quot;/proxy.sh&quot; ]</pre><p>The provisioning script is called with the ARGs supplied as environment variables at build time. A few other points worth noting:</p><ul><li>The AWS_USER argument is one I’ve defined to provide your AWS username, which is needed to set up MFA serials.</li><li>I’m using archlinux as my base image because pacman has all the dependencies I’ll need for this container, including aws-vault, with minimal fuss.</li><li>I’m configuring the container’s internal aws-vault to use the file vault backend, which is just a plain encrypted file supported on all platforms.</li><li>For now I’m using a hardcoded password for the vault, but plan to tighten security a bit by generating a random password at build time. Keep in mind, though, that this is intended only for local development, and that anybody with access to the insides of your local container can also read the environment of the process pretty easily anyway.</li><li>The ENTRYPOINT here shows how we launch the proxy inside the container, with an ECS server, under the local-developer role.</li><li><strong>NOTE: </strong>As-is, this won’t quite launch. It will complain that --ecs-server is not compatible with --prompt=terminal(the default). Don’t worry, we’re not done with this yet.</li></ul><h4>Provisioning the aws-vault server container</h4><p>The provision.sh script called by the Dockerfile does some vital things:</p><pre><em>#!/usr/bin/env bash<br><br># install dependencies<br>pacman </em>-Sy --noconfirm --needed \<br>  aws-vault \<br>  tinyproxy \<br>  which<br><br><em># create config dirs<br>mkdir </em>/root/.aws<br><em>mkdir </em>/root/.aws-vault<br><br><em># import aws config and add identity to vault from env<br>cat </em>/tmp/credentials | <em>sed </em>&quot;s/mfa\/$/mfa\/<em>$</em>{AWS_USER}/&quot; <em>&gt; </em>/root/.aws/credentials<br><em>cat </em>/tmp/config | <em>sed </em>&quot;s/mfa\/$/mfa\/<em>$</em>{AWS_USER}/&quot; <em>&gt; </em>/root/.aws/config<br><em>aws-vault </em>add --env myaccount</pre><ol><li>First the script installs dependencies we need. The first two are obvious, but which is not included in Arch’s docker image, and is required by some other things we’ll be running.</li><li>We create .aws and .aws-vault directories in root&#39;s home directory. The .aws folder is similar to the one in your own home directory. The .aws-vault directory is just a convenient place to stash the file-backed credential vault.</li><li>We could certainly use the local ~/.aws, but I opted to create a more constrained version of the config that only contains the local-developer role profile that the aws-vault service is meant to run. To make it generic to the user, I stripped the username off the mfa_serial values and then I just append ${AWS_USER} again at build time to create a full ARN.</li><li>We install AWS credentials into the container’s file vault using the --env flag to aws-vault add. This causes the access key id and secret access key to be read from the standard environment variables instead of prompting the user.</li></ol><p>As you can see from the ENTRYPOINT above, there is no longer any need to use --no-session inside the container. In an hour, when the current role credentials expire, aws-vault will just re-request your real credentials from the vault, generate a new session token, and be good for another hour. We’ve got the vault password in the environment, so no prompts for that either.</p><p>At this point we have a container that will continue generating credentials right up until aws-vault decides it needs your MFA token again, but that’s going to be relatively soon too, so there’s still one more thing to fix.</p><h4>Here’s my number, so MFA me</h4><p>You’ll recall I wrote earlier that there is a variety of “prompt” implementations available for aws-vault. You might be inclined to look for a way to use an environment variable or a script to get the token, and indeed it’s possible there’s a way to do it with credential_process, but I got an alternate solution working first.</p><p>One of the prompt methods aws-vault supports is pass, which is an open and highly Unix-ish CLI password manager backed by a GPG-encrypted store. Furthermore, there is a TOTP extension for pass, and aws-vault supports it. All we need to do is add our MFA seed to the pass vault and aws-vault can call that every time it needs to provide a new token.</p><p>Strangely enough, pass seems to be supported <em>only</em> for the MFA case, and not as a vault backing store, so we’ve got two credential stores going on in this container. Odd, but it works. The packages we need for this are also available in the Arch Linux pacman repo, so we only need a few modifications to get this working. In provision.sh:</p><pre><em># install dependencies<br>pacman </em>-Sy --noconfirm --needed \<br>  aws-vault \<br>  pass \<br>  pass-otp \<br>  tinyproxy \<br>  which</pre><pre>...</pre><pre><em># create the pass vault and insert the MFA serial<br>gpg </em>--quick-gen-key --yes --batch --passphrase &#39;&#39; aws-vault<br><em>pass </em>init aws-vault<br><em>pass </em>otp insert aws-vault &lt;&lt;&lt; <em>$</em>{AWS_MFA_URI}</pre><p>In the Dockerfile for the vault service:</p><pre>FROM<em> </em>archlinux<br>ARG<em> </em>AWS_MFA_URI</pre><pre>...</pre><pre>ENV<em> </em>AWS_VAULT_PROMPT=&quot;pass&quot;<br>ENV<em> </em>PASS_OATH_CREDENTIAL_NAME=&quot;aws-vault&quot;</pre><pre>...</pre><pre>RUN<em> </em>AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \<br>    AWS_MFA_URI=${AWS_MFA_URI} \<br>    AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \<br>    AWS_USER=${AWS_USER} \<br>    <em>/</em>provision.sh</pre><pre>...</pre><p>And then in docker-compose.yml:</p><pre>aws_vault_server:<br>  build:<br>    context: ./aws_vault_server<br>    args:<br>      - AWS_MFA_URI=${AWS_MFA_URI:-}</pre><pre>...</pre><p>You’ll need to supply this environment variable (in addition to the credentials provided by aws-vault when building the vault service).</p><p>The AWS_MFA_URI value needs to be an otpauth URI, which you might have directly for some services, but if you only have the bare serial value (which, for AWS, you probably will), you can reconstitute a URI like this:</p><pre><em>export </em>AWS_MFA_URI=&quot;otpauth://totp/aws-vault?digits=6&amp;secret=<em>$</em>{AWS_MFA_SERIAL}&amp;algorithm=SHA1&amp;issuer=AWS&amp;period=30&quot;</pre><p>You’re probably already using an MFA app (like Authy or Google Authenticator) to provide your MFA tokens, so you’ll need to reset your MFA to get your hands on the secret value. To do this:</p><ol><li>Log in to your organizational AWS account. If you’re like us, managing your MFA token is one of the few things you’ll have direct authorization to do.</li><li>In the console’s top-right dropdown, select “Security credentials.”</li><li>On the resulting “My security credentials” page, scroll down to the “Multi-factor authentication (MFA)” section and click “Manage MFA device.”</li><li>Remove and recreate your MFA device. Record the generator secret before adding it back to your MFA app of choice.</li><li>This value will be the value of AWS_MFA_SERIAL in the URI construction block above.</li></ol><h4>Tying it all together</h4><p>That’s pretty much it. To get the vault server running, you’d need to do something like this:</p><pre>$ AWS_MFA_SERIAL=&quot;&lt;your mfa secret&gt;&quot; aws-vault exec --no-session myaccount -- docker-compose build aws_vault_server</pre><p>When this container comes up and you attach other services to it, it will continue to provide up-to-date credentials with no prompting for as long as the service is running.</p><h3>Summary</h3><p>To sum up, I’ll provide the complete example files we built up over the course of this article.</p><p>The docker-compose command above invokes your project’s docker-compose.yml:</p><pre>version: &#39;3.9&#39;<br>services:<br>  aws_vault_server:<br>    build:<br>      context: ./aws_vault_server<br>      args:<br>        - AWS_MFA_URI=${AWS_MFA_URI:-}<br>        - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}<br>        - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}<br>        - AWS_USER=${AWS_USER:-}<br>    container_name: aws_vault_server<br>    networks:<br>      local_addr:<br>        ipv4_address: 169.254.170.2</pre><pre>  my_authorized_service:<br>    image: my_service_image:latest<br>    environment:<br>      AWS_CONTAINER_CREDENTIALS_FULL_URI: http://169.254.170.2/       <br>    networks:<br>      my_app_network:<br>      local_addr:</pre><pre>networks:<br>  my_app_network:<br>  local_addr:<br>    ipam:<br>      driver: default<br>      config:<br>        - subnet: 169.254.170.0/24</pre><p>The aws_vault_server service invokes the corresponding Dockerfile:</p><pre><em>FROM </em>archlinux<br><em>ARG </em>AWS_MFA_URI<br><em>ARG </em>AWS_ACCESS_KEY_ID<br><em>ARG </em>AWS_SECRET_ACCESS_KEY<br><em>ARG AWS_USER<br>ENV </em>AWS_VAULT_BACKEND=&quot;file&quot;<br><em>ENV </em>AWS_VAULT_FILE_DIR=&quot;/root/.aws-vault&quot;<br><em>ENV </em>AWS_VAULT_FILE_PASSPHRASE=&quot;XXXXXXXXXXXX&quot;<br><em>ENV </em>AWS_VAULT_PROMPT=&quot;pass&quot;<br><em>ENV </em>PASS_OATH_CREDENTIAL_NAME=&quot;aws-vault&quot;<br><em>COPY </em>files<em>/ /<br>RUN </em>AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \<br>    AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \<br>    AWS_USER=${AWS_USER} \<br>    AWS_MFA_URI=${AWS_MFA_URI} \<br>    <em>/</em>provision.sh<br><em>ENTRYPOINT </em>[&quot;aws-vault&quot;, &quot;exec&quot;, &quot;--ecs-server&quot;, &quot;--debug&quot;, &quot;local-developer&quot;, &quot;--&quot;, &quot;/proxy.sh&quot; ]</pre><p>During build, the Dockerfile runs provision.sh:</p><pre><em>#!/usr/bin/env bash<br><br># install dependencies<br>pacman </em>-Sy --noconfirm --needed \<br>  aws-vault \<br>  pass \<br>  pass-otp \<br>  tinyproxy \<br>  which<br><br><em># create config dirs<br>mkdir </em>/root/.aws<br><em>mkdir </em>/root/.aws-vault<br><br><em># import aws config and add identity to vault from env<br>cat </em>/tmp/credentials | <em>sed </em>&quot;s/mfa\/$/mfa\/<em>$</em>{AWS_USER}/&quot; <em>&gt; </em>/root/.aws/credentials<br><em>cat </em>/tmp/config | <em>sed </em>&quot;s/mfa\/$/mfa\/<em>$</em>{AWS_USER}/&quot; <em>&gt; </em>/root/.aws/config<br><em>aws-vault </em>add --env myaccount</pre><pre><em># create the pass vault and insert the MFA serial<br>gpg </em>--quick-gen-key --yes --batch --passphrase &#39;&#39; aws-vault<br><em>pass </em>init aws-vault<br><em>pass </em>otp insert aws-vault &lt;&lt;&lt; <em>$</em>{AWS_MFA_URI}</pre><p>In addition to adding the auth secrets into local vaults, this will copy the basic config and credentials files into the appropriate location, while putting your AWS_USER into the mfa_serial values.</p><p>credentials:</p><pre>[myaccount]<br>mfa_serial = arn:aws:iam::012345678901:mfa/<br>credential_process = aws-vault exec identity --json --prompt=pass</pre><p>config:</p><pre>[default]                                                                                                                                    <br>region = us-west-1                                                                                                                           <br>                                                                                                                                             <br>[profile myaccount]                                                                                                                           <br>mfa_serial = arn:aws:iam::012345678901:mfa/<br>                                                                                                                                             <br>[profile local-developer]                                                                                                                    <br>role_arn = arn:aws:iam::012345678901:role/LocalDeveloperRole                                                                             <br>source_profile = myaccount                                                                                                                      <br>mfa_serial = arn:aws:iam::012345678901:mfa/</pre><p>At runtime, the aws_vault_server container will execute:</p><pre>aws-vault exec --ecs-server --debug local-developer -- /proxy.sh</pre><p>This launches an ECS-style metadata server using the vault-stored credentials for both keys and MFA, and then provides the server’s AWS_CONTAINER_CREDENTIALS_FULL_URI and AWS_CONTAINER_AUTHORIZATION_TOKEN in the environment for /proxy.sh:</p><pre><em>#!/usr/bin/env bash<br><br>cat &gt; </em>proxy.conf &lt;&lt;<em>EOF<br></em>Port 80<br>Listen 0.0.0.0<br>AddHeader &quot;Authorization&quot; &quot;${AWS_CONTAINER_AUTHORIZATION_TOKEN}&quot;<br>ReversePath &quot;/&quot; &quot;${AWS_CONTAINER_CREDENTIALS_FULL_URI}&quot;<br>ReverseBaseURL &quot;http://169.254.170.2/&quot;<br>ReverseOnly Yes<br><em>EOF<br><br>tinyproxy </em>-c proxy.conf -d</pre><p>Every time my_authorized_service needs credentials for an operation, it will send a request to http://169.254.170.2/. This request will be forwarded to the internal metadata server, which will (as needed) construct new role credentials based on your stored AWS credentials, and request a current MFA token from pass, and then return a JSON blob containing a current, valid set of service credentials within LocalDeveloperRole.</p><p>Other services that need local developer access will therefore always have it as long as aws_vault_server is running, QED.</p><p>I got the sense while browsing for this solution myself that this was not obvious to a whole lot of people. It certainly wasn’t to me. I hope this helps your dev teams get maximum bang for their aws-vault buck by reducing time spent hassling over credentials for local development.</p><p>And in case it isn’t obvious, don’t try to use this in a non-local context. It’s <em>somewhat</em> secure, but the protections are a screen door at best against a serious attacker. Your AWS deployments already have a real metadata server available to them.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b1e3166daf46" width="1" height="1" alt=""><hr><p><a href="https://blog.formswift.com/indefinitely-long-lived-aws-sessions-using-aws-vault-and-docker-compose-the-guide-i-wish-id-had-b1e3166daf46">Indefinitely Long-Lived AWS Sessions Using aws-vault and docker-compose: The Guide I Wish I’d Had</a> was originally published in <a href="https://blog.formswift.com">FormSwift</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automate Your APIs With api-flow]]></title>
            <link>https://blog.formswift.com/automate-your-apis-with-api-flow-b1095eed0df7?source=rss----ca109bdff32c---4</link>
            <guid isPermaLink="false">https://medium.com/p/b1095eed0df7</guid>
            <category><![CDATA[api]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Rob Thomas]]></dc:creator>
            <pubDate>Mon, 14 Mar 2022 21:16:42 GMT</pubDate>
            <atom:updated>2022-03-14T21:16:42.573Z</atom:updated>
            <content:encoded><![CDATA[<p>Hi, I’m Rob Thomas (no, not <a href="https://en.wikipedia.org/wiki/Rob_Thomas_(musician)">that one</a>) (or <a href="https://www.imdb.com/name/nm0859432/?ref_=nv_sr_srsg_0">that one</a>). I’m a senior engineer at FormSwift, and it’s my privilege to write the first of what we hope will be many technical articles in this space.</p><p>For this first article, I’d like to describe a little project I’ve just published called “api-flow.” The project was inspired by a problem my wife (a QA Automation lead at another company) was running into. For reasons that would make sense if I felt free to go into detail, her company has a truly staggering number of sites that do much the same thing for many different audiences, but are different enough to need a certain amount of distinct infrastructure. And to test all this she’d been given a Postman collection.</p><p>So let’s talk about Postman.</p><p>We love Postman. Most everybody uses it, even though most of us never use half of what it’s grown into, which I shall generously call “highly feature-rich.” What it doesn’t do so well is run in CI, or run literally hundreds of variations of the same thing. It’s a big, heavyweight desktop app, and however much you’ve developed that collection, you’ve still got a person sitting in a chair poking a button and looking at the results. This is not a good use of people’s time at scale.</p><p>The first thing I did, of course, was to look for alternatives to Postman that could run headlessly. I’m still convinced that there has to be one out there I didn’t happen upon, but most alternatives I found were <em>also</em> big, unwieldy desktop apps. I’d set out expecting to decide which of several existing tools filled this niche best, and was surprised to come up empty.</p><p>Therefore I did what any sensible person wouldn’t do, and made one.</p><p>It’s early days for api-flow, with lots of potential areas of improvement, but it does a few basic things pretty well. The project runs “Flows” that are sequences of “Steps.” A Flow and its associated Steps are defined in YAML. Each Step represents a single API call. The headers, URL and body for a Step’s request are templates populated from a few sources:</p><ol><li>Any template variable can be satisfied by an environment variable. If an environment variable is defined, this takes precedence over most other sources.</li><li>When constructing a Flow, you can provide “profiles,” other YAML files that define a base set of variables accessible to every Step in the Flow.</li><li>Steps define “outputs,” which map variables to <a href="https://jsonpath.com">JSONPath</a> expressions. These are used to extract values from JSON response bodies. The extracted outputs are thereafter available to use in subsequent steps.</li><li>Very simple functions, including user-defined ones, are supported for more complex cases. A simple built-in function, for example, renders a random UUID.</li></ol><p>Flows can also depend on other Flows, which is a simple method for DRYing your Flow definitions. You can define a Flow that creates a user or logs in, and provides a session token as an output. Multiple other test cases can then depend on this common Flow and use its output.</p><p>When a Flow is complete, the instance still retains all of its collected outputs, still attached to their respective steps. You can use this to gather results for further processing, or you can assert on them in a unit test.</p><p>I have a few use cases in mind:</p><ul><li><strong>Automating day-to-day workflows:</strong> For example, as soon as I get a minute I plan to take a stab at automating some deployment tasks using the GitHub and Travis APIs.</li><li><strong>Accelerating standard QA Automation:</strong> Instead of walking through the UI flow to create a particular server state over and over for multiple tests, you could use api-flow to create the prerequisite state directly, and then drive the browser to test the only actual functionality you care about.</li><li><strong>Testing API-only stories:</strong> Rather than providing browser- or Postman-based testing instructions for stories that have no UI component, you can provide a Flow, which consumes a standard environment profile and exercises the desired behavior. The Flow can be reviewed alongside your regular code, and can be built into a complete suite of API tests that will work in all your environments with very little coding. You can provide simple instructions for manual testers to run the Flow at the command line and verify the results.</li></ul><p>The api-flow package can be found on <a href="https://github.com/rothomas/api_flow.git">GitHub</a> and <a href="https://pypi.org/project/api-flow/">PyPi</a>. For now it supports more or less exactly the set of features I have personally needed, but I look forward to growing it according to how it gets used, so feedback is highly encouraged.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b1095eed0df7" width="1" height="1" alt=""><hr><p><a href="https://blog.formswift.com/automate-your-apis-with-api-flow-b1095eed0df7">Automate Your APIs With api-flow</a> was originally published in <a href="https://blog.formswift.com">FormSwift</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Step-by-Step Guide to Form 1099-NEC]]></title>
            <link>https://blog.formswift.com/1099-nec-form-guide-f86ed34177ec?source=rss----ca109bdff32c---4</link>
            <guid isPermaLink="false">https://medium.com/p/f86ed34177ec</guid>
            <category><![CDATA[forms]]></category>
            <category><![CDATA[taxes]]></category>
            <category><![CDATA[freelancers]]></category>
            <dc:creator><![CDATA[Jackson Hille]]></dc:creator>
            <pubDate>Tue, 08 Dec 2020 06:27:35 GMT</pubDate>
            <atom:updated>2020-12-08T06:27:35.010Z</atom:updated>
            <content:encoded><![CDATA[<h3>What is a 1099-NEC?</h3><p>The 2021 calendar year comes with changes to business owners’ taxes. The new IRS form 1099-NEC is how employers will report non-employee compensation, instead of reporting these payments in box 7 on <a href="https://formswift.com/1099-misc">Form 1099-MISC</a>. Form 1099-MISC still exists, but it is just to list miscellaneous income, <a href="https://www.shrm.org/resourcesandtools/tools-and-samples/hr-qa/pages/what-is-the-irs-form-1099-nec-and-form-1099-misc.aspx">like prizes and awards or medical payments</a>.</p><p>Additionally, a 1099-NEC form is still a different from a <a href="https://formswift.com/w2">W-2</a>, which is provided to employees whose taxes are withheld by their employer. A 1099-NEC will be provided to vendors or sub-contractors who worked for a period of time for an employer to complete a project, with total payments exceeding $600. Independent contractors or self-employed contractors must still fill out a <a href="https://formswift.com/w9">Form W-9 </a>offered by IRS in their <a href="https://www.irs.gov/pub/irs-pdf/f1099nec.pdf">tax forms section</a>.</p><p>Employers, or payers, are required to file Copy A of the Form 1099-NEC with the IRS by January 31st, by mail or e-file.</p><h3>1099-NEC Definition</h3><p>The new Form 1099-NEC is for employers to document non-employee compensation for independent contractors and freelancers. Independent contractors must receive Copy B of the 1099-NEC in order to include it with their tax returns.</p><p>These conditions must be met to report payment for non-employee compensation:</p><ul><li>The payment made to someone other than an employee, such as an independent contractor or freelancer who completes a temporary assignment.</li><li>The payment must be made for projects and services in the course of your trade or business.</li><li>The payments made to the payee must be at least $600 during the year.</li></ul><p>As aforementioned, payers are required to provide a Form 1099-NEC to the payee and file it with the IRS by January 31st of the following year.</p><h3>1099-NEC Versus 1099-MISC</h3><p>Prior to the 2020 tax year, payments made to independent contractors or self-employed contractors were reported in box 7 on Form 1099-MISC.</p><p>With the introduction of the 1099-NEC, the information reported on box 7 of the old 1099-MISC — namely payments totaling over $600 — will now be reported on the 1099-NEC.</p><p>The 1099-MISC still remains, but it will now just be used for reporting miscellaneous income, such as rent and healthcare payments.</p><p>Due to the anticipated confusion for the new form, there will probably be filers who need an extension to complete a 1099-NEC. For a taxpayer to file for an extension, they must meet the filing requirements from <a href="https://www.irs.gov/pub/irs-pdf/f8809.pdf">Form 8809</a>. Here are some of the filing requirements:</p><ul><li>The filer suffered a catastrophic event in a federally declared disaster area that made the tax filer unable to keep their business operating or made the records they need to file taxes unavailable.</li><li>Fire, causality, or natural disaster affected the operation of the filer, and;</li><li>The death, contracting a serious illness, or unavoidable absence of the individual responsible for filing the information returns affected the business of the filer.</li></ul><h3>How to Fill Out a 1099-NEC</h3><p>Both the payer’s and the recipient’s information includes both parties name, address, and taxpayer ID. Non-employee payment should include the total compensation for the past tax year. This amount is the gross proceeds.</p><p>As the payer, you will also need to include:</p><ul><li>Federal income tax withheld, if any</li><li>An optional account number</li><li>Check the FATCA filing box, if applicable</li><li>State tax withheld, if any</li><li>State/payer’s state number</li><li>State income</li></ul><p>Both parties involved provide their names, address, and taxpayer identification numbers. Non-employee compensation is listed in Box 1 of the form. This should be the total compensation for the past tax year in the form of gross proceeds.</p><p>It is important to note that Box 4 is for federal income tax withheld. Even though this is uncommon for most instances when you hire non-employees unless you received a backup withholding order for that person, backup withholding requires a payer to withhold tax from payments not otherwise subject to withholding. There is a list of backup withholding rules on the IRS website at <a href="https://www.irs.gov/businesses/small-businesses-self-employed/backup-withholding">IRS.gov</a>.</p><p>Boxes 5 through 7 are listed for your convenience only according to the official instructions provided by the <a href="https://www.irs.gov/pub/irs-pdf/i1099msc.pdf">IRS website</a>. You can use each set of boxes to provide information for up to two separate states. This information is found in the same instructions as the instructions for the 1099-MISC (directly below it).</p><p>Box 5 is for any state tax that <a href="https://www.irs.gov/pub/irs-pdf/i1099msc.pdf">you pay to the provider for their services</a> if it is required by your state.</p><p>Box 6 is for you, as the payer to list the identification number of the state to which you are reporting the tax information.</p><p>Box 7 allows you to list the amount of the state payment.</p><h3>1099-NEC Filing Rules for Employers</h3><p>Regardless of the size of the business, the IRS states businesses should file a 1099-NEC form for each person whom they paid at least $600 if they:</p><ul><li>Performed services by someone who is not their employee (including parts and materials) by listing this in Box 1.</li><li>Cash payments for fish or other sea-based life bought from anyone involved in the business of catching fish. An example of this is a restaurant that purchases fish from a fish market that may employ fishing boats.</li><li>Made payments for legal advice from an attorney.</li></ul><p>Businesses must also use the new Form 1099-NEC if they withheld any federal income tax under the backup withholding rules regardless of the type of payment made.</p><h3>Sample 1099-NEC</h3><p>The 1099-NEC is for non-employee compensation and is not for miscellaneous income. When you fill out a Form 1099-NEC you will fill out:</p><ul><li>The payers’ name, street address, city or town, state, country, zip code or foreign zip code, and telephone number.</li><li>Box 1 is non-employee compensation listed as gross proceeds.</li><li>Next, you will need to fill out the payer’s tax identification number.</li><li>Then, include the Recipient’s tax identification number.</li><li>Boxes 2 and 3 are left blank.</li><li>Next, list the recipient’s name.</li><li>Then, list the recipient’s full address including an apartment number or suite number, if necessary.</li><li>Box 4 is used to list the amount of federal income tax withheld if any. When preparing tax forms for non-employees, federal income tax is often not withheld unless there is an order to do so by the court or the IRS.</li><li>Next, list the recipient’s city, state, country, and zip code or foreign postal code.</li><li>If you have an account number, you may include it toward the bottom of the form. This is an identifying number you assigned to identify the account.</li><li>Box 5, 6, and 7 are optional for you. They are to use to document state tax withheld, state and payer’s number, and the state income.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f86ed34177ec" width="1" height="1" alt=""><hr><p><a href="https://blog.formswift.com/1099-nec-form-guide-f86ed34177ec">A Step-by-Step Guide to Form 1099-NEC</a> was originally published in <a href="https://blog.formswift.com">FormSwift</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dear New Grad UX Designers,]]></title>
            <link>https://blog.formswift.com/dear-new-grad-ux-designers-248c1a09675c?source=rss----ca109bdff32c---4</link>
            <guid isPermaLink="false">https://medium.com/p/248c1a09675c</guid>
            <category><![CDATA[ux-design]]></category>
            <category><![CDATA[work]]></category>
            <category><![CDATA[product-design]]></category>
            <category><![CDATA[design]]></category>
            <category><![CDATA[ux]]></category>
            <dc:creator><![CDATA[Pamela Hu]]></dc:creator>
            <pubDate>Mon, 05 Aug 2019 22:07:16 GMT</pubDate>
            <atom:updated>2019-08-05T22:07:16.709Z</atom:updated>
            <content:encoded><![CDATA[<h4>These are 4 main changes you might experience once you start work…</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VZkw1KyI7sBihW1dmQCmlQ.jpeg" /><figcaption><a href="https://unsplash.com/photos/Oalh2MojUuk">Source</a></figcaption></figure><h3>1. More Targeted Problems to Solve</h3><p><em>“Redesign Youtube.” “Create a recipe app for millennials.” “Design a health app.”</em></p><p>In classes and internships, my assignments typically spanned months in time and covered every step in the design process. The deliverable could look like 30+ screens.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/913/1*icHGXo1Yf1jUso9txGiXeg.png" /></figure><p>Now, at work, I’m given more targeted problems. In a past assignment, I focused on this sample document section — just a small piece of <a href="https://formswift.com/letter-of-recommendation#download">this entire page</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U0wpc6NW3xRZHLQECUCjeg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aHpvwnd5qNpa1sRQeQCvCA.png" /></figure><p>My PM, Mike, revealed to me that more users clicked on the gray boxes within the document, which triggers nothing, than on the “Create Document” button, which triggers the desired next step.</p><p>A poor user experience if you expect something to be clickable and it isn’t, sure, but also how can we funnel more users into creating their document?</p><p>We landed on this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oEkv3d3x5qhtcPHRi4xYfg.png" /><figcaption>View this page live, <a href="https://formswift.com/letter-of-recommendation#download">here</a></figcaption></figure><p>When a user tries clicking anywhere on the document, the tool tip pops up exactly where a user is suppose to “Start”. Yup. That’s it.</p><p>While our solution seems simple, the thought process behind it was not.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/930/1*JJ9tBxBw8Q8jhU2FAFYEIQ.png" /></figure><p>Not too long ago, I would’ve thought that 3 full days on a simple pop up was overkill but the more experience I have, the more nuance I have to contemplate to get to the final solution.</p><p>Also instead of creating experiences and screens from scratch, I’m usually designing on top of or within current designs — iterating to optimize our product. This requires understanding constraints and how to more naturally integrate something new into something existing.</p><h3>2. More Cases to Consider</h3><p><em>“Should we have rounded or unrounded corners?”</em></p><p>In college, I focused much more on how designs looked than how they behaved. Even considering the UX, I’d mock up what was suppose to happen but not what could happen, the expected default version but not the exceptions and edge cases.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KjaJt6Fc5m7LSYr07kpdMA.png" /><figcaption>Eye candy.</figcaption></figure><p>This meant that, in college, had I designed a dropdown field I would’ve stopped here:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FL9J6VPJ1SN2MCte2jQ98Q.png" /></figure><p>I acknowledged the dropdown’s:</p><ul><li>Shape</li><li>Border radius</li><li>Border width</li><li>Border color</li><li>Font family</li><li>Font size</li><li>Font weight</li><li>Icon</li></ul><p>But… what happens after the dropdown has been clicked?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C_DigAeA-_O-wAv9TS9bqA.png" /></figure><p>What happens after a choice has been selected?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NWLvCiO_fVn434cATuH3bw.png" /></figure><p>What happens when the user wants to go back to double check their work and… <em>“Wait, what was this state referring to?”</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4Ldpio857XET5maMKvVkcQ.png" /></figure><p>What happens:</p><ul><li>If an option is 40 characters long?</li><li>If the user forgot to make a selection?</li><li>If the user made the wrong selection?</li></ul><p>How will this dropdown change if placed on a mobile screen, on a different colored background? Will users with accessibility issues be able to see, read, and click into it?</p><p>This is just a teaser of what our design team has had to ask in creating our new design system:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZKiW-vlcv_BIIB0ca6La3Q.png" /></figure><p>And there are constantly new cases or inconsistencies we’re discovering — a reminder that design, too, is a living being that changes and grows.</p><h3>3. Compromising with Business &amp; Engineering Goals</h3><p><em>“Ads make it look so cluttered.” “Lets have these really cool animations as you scroll down.”</em></p><p>In college, I was like “Okay, there’s the business side and the engineering side too…I know…” But I didn’t know-know, ya know?</p><p>Design has to improve the user experience but it also has to benefit the business and be worthwhile enough to be developed by engineers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7CJC-ib1dqmmu8wqPArTNQ.png" /><figcaption>Pamela, a designer, sharing a neat design to Max, an engineer</figcaption></figure><p>At work, I helped redesign our Document Library. Given a page that mostly does the job already, how do we make a convincing enough case for updating it?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vz6zil0VJCpGjKZsxmgo9Q.png" /></figure><p>By splitting it up.</p><p>Because if at any step, the design fails —we can still pull back without engineers having invested too much effort, without the business having invested too many resources. Overall, less risk.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*34BElzHxWW2bEX2StV9WZA.png" /></figure><p>So the design will be rolled out in stages. In fact, the first update isn’t even noticeable at a glance because it’s a change to the behavior of the search bar.</p><p>If we find that users are more easily able to find and create the document they want, then we’ll give the UI a makeover:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZuwxBpngpdwjcUCrDHP2ng.png" /></figure><p>We also have designs mocked up for the other pieces of this page but they’ll only get rolled out if certain metrics are achieved— an unthinkable concept for the college-designer-me…</p><h3>4. Getting Hit with the User Feedback</h3><p><em>“Nice presentation! Does anyone have comments or questions?”</em></p><p>👆That’s usually where it ends for design projects in schools or internships. But in a company, designs don’t just end with a prototype. Developing and releasing designs to actual users is just the beginning of a feedback loop that guides the iterative process.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4P__NBc9bVNKBw4F6NEICA.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@neonbrand?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">NeONBRAND</a> on <a href="https://unsplash.com/search/photos/class-presentation?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>I thought this could be best illustrated as a meme, and this is the first meme I’ve ever made. Wow. I am both proud and embarrassed by this feat:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/857/1*2iH_blTlHVKNhyKfM3A2bA.jpeg" /></figure><p>Let me break it down for you…</p><h4>Designing What You Want</h4><p>You know what I want? This, this right here:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wzA1XVdZ8DduKUcPeXKLaQ.png" /><figcaption><a href="https://www.kanarys.com/">kanarys.com</a></figcaption></figure><p>Quirky animations. Tasty colors. That sleek sans-seriff font. But alas I am not representative of our main user base…</p><h4>Designing What the Users Think They Want</h4><p>Users can be passionate about what works and what doesn’t work, mostly what doesn’t work. They love suggesting features they themselves would want. They might say things like,</p><blockquote>“I like minimal design, no clutter.”</blockquote><p>But also,</p><blockquote>“Why is the button hidden? It should be easier to find.”</blockquote><p>As designers, we’ll try our best to cater to the user — that’s our job— but it’s also our job to determine the right balance.</p><p>We need to ask: How much of a problem is this problem? Is it just a passionate few making it seem like a bigger problem than it actually is? And if we change this one thing, how will it affect the rest of the design? Is it worth it?</p><h4>Designing What You Think the Users Want</h4><p>Brittany, another Formswift designer, iterated a ton on this landing page for our new product.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oIWaOje5oq80QL2X0KMllg.png" /></figure><p>We conducted many user interviews on it and the design evolved based on feedback. Even then a lot of assumptions had to be made around what information the user should see, in what layout, in which order, and how much.</p><h4>Designing What the User Needs Based on the Actions They’ve Demonstrated by Using Your Product</h4><p>Once that landing page went live, you know what ended up happening?</p><p>Only a couple people even scrolled past the fold. Almost all users clicked the “Start” button right away.</p><p>So more appropriately, the landing page should’ve looked like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*y981WqW72ThK0OIG78itBQ.png" /></figure><p>Ha.</p><p>I’m (mostly) joking! But I hope you get the point.</p><p>Even as designers, who pride ourselves in user empathy, it’s easy to get caught up in the “ideal” user — one who will read through every single word, stare at every single illustration, and comprehend everything before moving to the next page.</p><p>But by utilizing feedback, we can create a stronger solution that takes into account users’ realistic behaviors and tendencies.</p><h3>With all that said…</h3><p>My “preachings” are my own learnings in disguise. I say my “own” but these learnings couldn’t be fully realized without my team — Mike, Brittany, Ronnie, and Elisa.</p><p>This is not an exhaustive list and experiences will undoubtedly vary. For context, this is the point of view I’m coming from:</p><ul><li>🐻 I graduated from <a href="https://medium.com/u/255942f15b34">UC Berkeley</a>, which offered no design major but a growing number of design classes and extracurriculars that I took part in. I had several summer design internships in the Bay Area.</li><li>🎉 I <em>just</em> passed my <strong>1 year</strong> mark as a <strong>designer</strong>! I’ve taken time to reflect on learnings and observe patterns (which hopefully show through in this post) but I understand there’s a lot I still don’t know.</li><li>📑 I work at <a href="https://medium.com/u/634186a0f052">FormSwift</a>, a <strong>San Francisco start up</strong>. We work on online legal and tax documents, e-signing, PDF editing — the whole shabang.</li></ul><h3>Congrats to the new wave of designers!</h3><p>Your world is gonna get rocked! 🤘I think our #design channel says it best:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/922/1*bYDxh4h7WbMUUMu6ZAonqA.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=248c1a09675c" width="1" height="1" alt=""><hr><p><a href="https://blog.formswift.com/dear-new-grad-ux-designers-248c1a09675c">Dear New Grad UX Designers,</a> was originally published in <a href="https://blog.formswift.com">FormSwift</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Contradicting My Own Design Advice]]></title>
            <link>https://blog.formswift.com/contradicting-my-own-design-advice-12af9a5bb91a?source=rss----ca109bdff32c---4</link>
            <guid isPermaLink="false">https://medium.com/p/12af9a5bb91a</guid>
            <category><![CDATA[design-thinking]]></category>
            <category><![CDATA[product-design]]></category>
            <category><![CDATA[ui]]></category>
            <category><![CDATA[design]]></category>
            <category><![CDATA[ux]]></category>
            <dc:creator><![CDATA[Pamela Hu]]></dc:creator>
            <pubDate>Mon, 08 Jul 2019 22:58:21 GMT</pubDate>
            <atom:updated>2019-07-08T22:58:21.728Z</atom:updated>
            <content:encoded><![CDATA[<h4>How I filled an empty state full to the brim and what I learned from watching it overflow…</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wKqXbOLqISoFSnYNkrwiOA.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@christinhumephoto?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Christin Hume</a> on <a href="https://unsplash.com/search/photos/work?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>In <a href="https://blog.formswift.com/when-users-put-fruit-in-a-coffee-maker-find-the-appliance-useless-d21bd30939ee">my last post</a>, I explained:</p><p><em>We just launched a new product. New to us. New to the industry.</em></p><p>This new product being…</p><p><em>The Template Builder…our crack at streamlining the back-and-forth paperwork process that hundreds of businesses waste hundreds of hours on. This product lets users add fields on top of their existing document to create one template that is reusable and easy for their recipients to fill out.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dV7LYzQ3IW104_UKnabvTw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4BxmRzOVWe6l3mYnVFeTLA.png" /><figcaption>(L) User building the template (R) What user’s recipient will see</figcaption></figure><p>For the past few months, we’ve been trying to figure out the best way to introduce this product. This is just one of our many attempts.</p><h3>Look here, look here!</h3><p>Before we keep someone’s attention, we must first capture it. How? Well, people typically want to understand what’s relevant<em> to them;</em> and it’s easier for people to imagine how a new product can fit into their existing workflow than how their existing workflow can accommodate a new product.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/977/1*HSQ3UaT0-PhuGxbNDEq6Xw.png" /><figcaption><a href="https://medium.com/u/f9ec0e0e5d0">Typeform</a> served as inspiration</figcaption></figure><p>So before we spew out messaging around our solutions and value props, we’ve got to call out the relevant users with workflows we can help streamline!</p><h3>My Proposal</h3><p>Of those who discovered the Template Builder, almost all came from spotting this page — a powerful leverage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/899/1*7dbL1pF4BJcRH0HaZgfgpw.png" /><figcaption>Original empty state</figcaption></figure><p>The original empty state has no mention of user groups so lets:</p><ol><li><strong>Highlight our 3 main user groups: </strong>Business, Real Estate, and Human Resources</li><li><strong>Provide the most relevant sample document for each respective user group </strong>to convey “<em>this is the type of document you need</em> <em>to upload”</em> for the Template Builder to make sense</li><li><strong>Convey that there is a recipient role</strong> because in user testing this crucial point was often missed</li></ol><p>My design ended up looking like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*koWzmpw5d9o-DTRhoDSyAg.png" /><figcaption>New empty state</figcaption></figure><h3>My Justification</h3><p>I thought this proposal would work because:</p><ol><li>More qualified uploads could come from more users realizing this product is relevant to them</li><li>More qualified uploads could come from users seeing examples of the right types of documents to upload</li><li>At the very least, I could filter out the wrong types of documents to upload</li></ol><h3>And…It failed!</h3><p>In the 1–2 weeks that the new empty state was live, almost no one uploaded a correct PDF, our main success metric. Compared to the original empty state, our click rates on the “Try a Demo” CTA went down by about 50%, a secondary success metric.</p><p>Yikes. 🙃</p><h3>What went wrong?</h3><p>Hindsight is 20/20 so let’s backtrack. I’ll never know for sure but I did embarrassingly realize…</p><blockquote>I went against all <a href="https://blog.formswift.com/when-users-put-fruit-in-a-coffee-maker-find-the-appliance-useless-d21bd30939ee">the advice I gave in my last post</a>! Ha!</blockquote><h4>Advice #1: “If everything is important, then nothing is.”</h4><p>When I was drafting what content I should include, I made a table to organize what relates to what and noticed a “formula”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mymp7GkEfxq1Nuc_1B6skg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/620/0*Y__wo6_1C5F7s7OF.jpg" /></figure><p>I thought:</p><blockquote>Hey! Wouldn’t it be cool to have some fill in the blank layout like Mad Libs?</blockquote><p>This could suggest that there are many different ways to use the product and the user can plug in their own use case.</p><p>The copy, below on the left, identified the user, their document, and their recipient. In the next iteration, below on the right, I wanted to also address the pain point.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_evN_tvkna3qzBHG4Wrq9A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lAICKEI_zIOHGIVUHkKvfA.png" /></figure><p>Then I wanted to also specify what types of documents a user should be using with the Template Builder:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Dbml-z8Z3rJI76QqzUGAeQ.png" /></figure><p>The example document, the recipient, the user group, the pain point, the types of document a user should be uploading — all thrown into the mix. I thought this was such a concise and efficient way to get our message across but the copy was trying to hit so many points that nothing landed.</p><h4>Advice #2: Think inside the box. Use the constraints of what you’re working in to your advantage.</h4><p>In the final version that went live on Formswift, I literally went outside the box. In hopes of being eye-catching, I think the visuals actually became distracting.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*koWzmpw5d9o-DTRhoDSyAg.png" /><figcaption>Default view</figcaption></figure><p>Because I didn’t use the constraints of a traditional empty state, typically a couple sentences of copy and 1 main CTA, my empty state resembled more of a landing page. This might’ve caused a disconnect —</p><p><strong>Expectation</strong>: This is a lot to consume and a lot to interact with…<br><strong>Reality</strong>: No, actually, it’s just an empty state.</p><p>Playing off the physical constraints of an empty state box, I could’ve created something like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OGuTpQDJciKBS4mStUhxwQ.png" /></figure><h4>Advice #3: Make the obvious OBVIOUS.</h4><p>What isn’t apparent right away is that the dropdown options are connected.</p><p>If a user clicked “Rental Application” in the first dropdown then “Tenants” would automatically get selected. The copy in the green CTA would also automatically change to “Try a Landlord Demo.” Each of the 3 different CTAs directs to a different sample document.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fM2G2JXEmp9BMYXUYtRB8A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VMMWNzYUmC_uO5wSr6Ll3A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_jg6aDfeto8iBKI1OtdNjg.png" /><figcaption>The 3 target user groups</figcaption></figure><p>Because this relationship isn’t clear until a user clicks around (and chances are, they wont), it seems like there’s a lot of work to be done. The original empty state had 1 “Try a Demo” CTA. Now, 3? As Hick’s Law puts it:</p><blockquote>Increasing the number of choices will increase the decision time logarithmically.</blockquote><p>Other possible concerns: does it look like <em>only</em> these 3 documents would work? Does it look like we’re making the documents for you? What if the user doesn’t identify with any of the user groups?</p><p>The more chances for assumptions, the more chances for users to mis-assume and click out.</p><h3>Final Takeaways</h3><ol><li>Sometimes, in this case, less is more!</li></ol><p>2. It’s important to separate the idea from the execution. Although we learned that the highly-illustrated-double-drop-down-empty-state didn’t work, that doesn’t mean user personas don’t!</p><p>3. Last but not least, take this as a chance to reflect on your own work. Realize how challenging it is to remain consistent in ideals and how productive it can be to identify not just what failed by why.</p><p>Onwards and upwards! 📈</p><h3>Get Your Hands Dirty</h3><p>The Template Builder is very much <a href="https://formswift.com/self-serve-builder">here</a> and alive if you want to play around with it. You’ll need an account first — it’s free!</p><h3>Thankful</h3><p>For my team — especially Elisa, Ronnie, and Brittany. ❤️</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=12af9a5bb91a" width="1" height="1" alt=""><hr><p><a href="https://blog.formswift.com/contradicting-my-own-design-advice-12af9a5bb91a">Contradicting My Own Design Advice</a> was originally published in <a href="https://blog.formswift.com">FormSwift</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[When Users Put Fruit In a Coffee Maker & Find The Appliance Useless]]></title>
            <link>https://blog.formswift.com/when-users-put-fruit-in-a-coffee-maker-find-the-appliance-useless-d21bd30939ee?source=rss----ca109bdff32c---4</link>
            <guid isPermaLink="false">https://medium.com/p/d21bd30939ee</guid>
            <category><![CDATA[ui]]></category>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[ux]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[design]]></category>
            <dc:creator><![CDATA[Pamela Hu]]></dc:creator>
            <pubDate>Thu, 27 Jun 2019 23:22:45 GMT</pubDate>
            <atom:updated>2019-06-27T23:47:24.549Z</atom:updated>
            <content:encoded><![CDATA[<h4>An analogy for the problem we’re facing and a case study at how to explain “You actually need coffee beans to make it work!”</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BdEMjnADvQDFMXg3Cf5o9w.jpeg" /><figcaption><a href="https://unsplash.com/photos/TD4DBagg2wE">Source</a></figcaption></figure><p>We just launched a new product. New to us. New to the industry. In our asking “how do we get people to use it?” we must first answer “how do we get people to understand it?”</p><h3>So what’s this “new product”?</h3><p>No, it’s not a coffee maker — it’s the Template Builder and it’s our crack at streamlining the back-and-forth paperwork process that hundreds of businesses waste hundreds of hours on.</p><p>This product lets users add fields on top of their existing document to create one template that is reusable…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dV7LYzQ3IW104_UKnabvTw.png" /><figcaption>This is what users see when building their template.</figcaption></figure><p>and easy for their recipients to fill out.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4BxmRzOVWe6l3mYnVFeTLA.png" /><figcaption>This is what user’s recipients will see when filling out the form. Users get back the PDF on the right.</figcaption></figure><h3>The Analogy</h3><p>With limited context, the first step to successfully using, no — just understanding, the Template Builder is uploading the right type of document. I mean…</p><blockquote>Imagine filling a coffee maker with fruit and expecting a smoothie. You won’t get the type of drink you want and the coffee maker will seem useless.</blockquote><p>In our case, the “Template Builder” is the “coffee maker” and “documents with blank fields for someone to fill out and send back” are the “coffee beans.” So peer evaluation forms, freelancing contracts, job applications — all different types of coffee beans and all qualified for the Template builder! A “fruit” would be any PDF you need to edit (we have <a href="https://formswift.com/sem/edit-pdf">a different tool</a> for that.)</p><h3>What’s Really Going On</h3><p>Simply put, the Template Builder workflow looks like:</p><ol><li><strong>Upload a qualified PDF (“coffee beans”)</strong></li><li>Add fields on top of the PDF to make the template</li><li>Send out the template</li><li>Receive responses from the template</li></ol><p>Our data showed that:</p><ul><li>No one with an unqualified document completed this workflow</li><li>Every person who finished this workflow started with a qualified document</li></ul><p>So this current assignment is <strong>focused on increasing the number of qualified uploads </strong>because without the correct type of document, there’s little chance for the user to understand and continue on with the product.</p><h3>Sample Document: Our First Attempt</h3><p>We thought a sample document would be a good idea to test because this can:</p><ul><li>Serve as a demo of sorts, on-boarding even</li><li>Explicitly illustrate the type of document users should be uploading</li><li>Act as a perfect example of what the Template Builder can do</li><li>Prevent users from uploading random documents just to test the new product out (which we saw happening) — the equivalent of adding fruit to the coffee maker!</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9vVN4zPDkMWRvjagTiurzA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*T9ZlW6WnH38CGGx4OtjNaQ.png" /><figcaption>Ronnie took the first stab at this sample document.</figcaption></figure><p>After a couple weeks of testing the sample document that Ronnie (one of our designers) did, we realized that:</p><ul><li>Almost no one completed the instructions on the document</li><li>A lot of the contract is cut out of frame</li><li>There’s no clear starting point</li></ul><p>And so we made the assumption moving forward that:</p><blockquote>Users want to do as little as quickly as possible to learn about the new product; and they would rather upload their own documents right away.</blockquote><h3>Sample Document: Another Attempt</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*loFjLI4IZeYi2_CGfV8ZjQ.png" /></figure><p>We saw some improvements in the number of qualified uploads and decided to squeeze more juice out of the sample document idea.</p><h4>Update #1: Accommodating for Size Restraints</h4><p>While reducing the negative space and font size of the sample document, I realized the sample document didn’t have to be the uploaded document — meaning, I could have a document within a document…</p><p>This would allow me to fit the first few instructions above the fold.</p><p>And the instructions could be placed right by what the instruction was referring to. For instance, the “Click Preview” instruction is positioned and pointing at the actual “Preview” button.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/834/1*hGrrJJuPGTr91xlToX51sQ.png" /></figure><blockquote><strong><em>Takeaway</em></strong>: Think inside the box. Use the constraints of what you’re working in to your advantage.</blockquote><h4>Update #2: Guiding the User in the Right Direction</h4><p>At first the copy was in a backward “7” layout, starting right to left then top-down. This felt unnatural, so I moved all the text into a single vertical column.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yI39mghZrZ1BET_cYvWz6Q.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rAQDL8Ri0hfGQ_joWuIOZQ.png" /></figure><p>The instructions were made easier to find when they were grouped by a contrasting background color, a dark blue.</p><p>Lastly, I thought “Step 1, 2, 3…” could imply the starting point. I even circled the text in a bright red. But this “implicit” design requires an extra step of thinking for the user so I added the actual word “Start.” The text’s size, weight, and casing were all used to establish information hierarchy.</p><blockquote><strong><em>Takeaway</em></strong>: Utilize the natural flow of reading and visual perception to make the work you want your users to do less taxing. Oh, and make the obvious OBVIOUS.</blockquote><h4>Update #3: Reducing Action Steps and Copy</h4><p>To reduce, we really had to prioritize what instructions were the most important.</p><p>We conducted many user interviews before we launched the product and realized the “Ah-ha!” moment is when users saw the “Preview” for what recipients see. This made labeling all the fields make sense. So the first two steps were dedicated to identifying and explaining the “Preview.”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Pkg-zCpJjTbuUqj_kAh0fg.png" /></figure><p>From the previous sample document, we noticed that almost no users wanted to interact with the tools.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/145/1*wcz14vXtRlU3bRoAJ7kaaA.png" /></figure><p>The most common action after leaving the sample document was uploading a document. We decided to make this experience even easier by having an upload at the very bottom.</p><p>Why not at the top with the rest of the prominent CTAs? We want to encourage users to upload a document but still, at the very least, glance through the sample document instructions before doing so.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/829/1*zNfppzHBZg4dpptxQY-ulg.png" /></figure><blockquote><strong>Takeaway</strong>: “If everything is important, then nothing is.” If someone can only remember 1 thing about your teachings, what would you like it to be?</blockquote><h3>The Final Version &amp; How We’ll Test It</h3><p>So this is what we have for now.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o8bxMU__qa3LyvDSiGDqTw.png" /></figure><p>We’ll look at how many users click “Upload” and, of these, how many upload a qualified document after viewing the sample document. We’ll also consider the number of clicks on “Preview” and pre-made fields and if any user makes their own field on the sample document.</p><p>Now, fingers crossed for correctly brewed coffee… 🤞☕️</p><h3>Make Yourself a Cup of Joe</h3><p>All this yibber yabber about the Template Builder… might as well link it <a href="https://formswift.com/self-serve-builder">here</a>. You’ll need an account (it’s free!) and some coffee beans…</p><h3>A Group Effort</h3><p>A medium article and project not possible without the insight, guidance, and critiques from my team — especially Elisa, Ronnie, and Brittany. Thank you for the pep you give to my steps! 🤪</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d21bd30939ee" width="1" height="1" alt=""><hr><p><a href="https://blog.formswift.com/when-users-put-fruit-in-a-coffee-maker-find-the-appliance-useless-d21bd30939ee">When Users Put Fruit In a Coffee Maker &amp; Find The Appliance Useless</a> was originally published in <a href="https://blog.formswift.com">FormSwift</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Trump Family Travel vs Puerto Rico Aid Study]]></title>
            <link>https://blog.formswift.com/trump-family-travel-vs-puerto-rico-aid-study-2a932b47c035?source=rss----ca109bdff32c---4</link>
            <guid isPermaLink="false">https://medium.com/p/2a932b47c035</guid>
            <category><![CDATA[politics]]></category>
            <category><![CDATA[trump]]></category>
            <category><![CDATA[trump-administration]]></category>
            <category><![CDATA[hurricane-maria]]></category>
            <category><![CDATA[puerto-rico]]></category>
            <dc:creator><![CDATA[Maya Wald]]></dc:creator>
            <pubDate>Thu, 05 Oct 2017 18:01:01 GMT</pubDate>
            <atom:updated>2017-10-05T18:01:01.480Z</atom:updated>
            <content:encoded><![CDATA[<p>We determined how many times the Trump family’s taxpayer-funded travel in 2017 could have gotten a US cargo ship of aid from Florida to victims in Puerto Rico.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4xj_TeG1zjxUKdbpCCNiSQ.png" /></figure><p>Methodology:</p><p>We wanted to see how much it would cost (in terms of fuel) to send the largest running US cargo ship to Puerto Rico from Florida with relief aid. We determined the distance between Florida and Puerto Rico to be 1149.537 miles. At .0046 nautical miles to the gallon, this trip would require 249899.347 gallons of fuel. At approximately $321 per gallon, the total amount to send this cargo ship of aid one-way would be $249,899.</p><p>Because of Trump’s recent statements regarding hurricane Maria and the ‘very large ocean’ between us, as well as ongoing news regarding his family’s travel expenditures, we decided to look at how many times the Trump family’s taxpayer-funded travel could have gotten the aforementioned cargo ship between Florida and Puerto Rico. We analyzed government reports on Trump family travel spending, and detailed each family member’s travel expenditures by occasion and cost to determine the total amount for each individual. Whenever possible we split up these travel expenditures by costs specific to airfare. We divided the total cost per individual by $249,899 to find out how many times (or how far) the cargo ship could get with just these funds.</p><p>Take President Trump for example. Trump took four trips to his Mar-a-Lago resort between February 3rd and March 19th. His airfare alone cost between $580,000 and $812,000 per trip. Trump’s non Mar-a-Lago travel expenses cost taxpayers a total of around $29,000,000, totaling around $32,000,000. When we divided this total by $249,899, we determined that Trump’s travel alone could have paid for a cargo ship to bring relief aid from Florida to Puerto Rico approximately 128 times.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2a932b47c035" width="1" height="1" alt=""><hr><p><a href="https://blog.formswift.com/trump-family-travel-vs-puerto-rico-aid-study-2a932b47c035">Trump Family Travel vs Puerto Rico Aid Study</a> was originally published in <a href="https://blog.formswift.com">FormSwift</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[FormSwift for Puerto Rico]]></title>
            <link>https://blog.formswift.com/helping-puerto-rico-35ef846ee15?source=rss----ca109bdff32c---4</link>
            <guid isPermaLink="false">https://medium.com/p/35ef846ee15</guid>
            <category><![CDATA[puerto-rico]]></category>
            <category><![CDATA[charity]]></category>
            <dc:creator><![CDATA[Jackson Hille]]></dc:creator>
            <pubDate>Thu, 28 Sep 2017 21:01:47 GMT</pubDate>
            <atom:updated>2017-09-28T21:01:46.624Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*63hJhz_n7OH6L3HbBbUemg.png" /></figure><p>In the past few weeks, our world has been inundated with horrific natural disasters — Hurricanes Harvey, Irma, and Maria, the floods in Somalia, and the earthquake in Mexico. This nation did a great job of raising funds for Harvey and Irma relief, but in recent days the devastation that has hit Puerto Rico and its people, who are U.S. citizens, has gotten less attention than it deserves. Moreover, one of our team members, Marisabel Agosto is Puerto Rican and we want to help her, her family, and all of our fellow citizens out during these trying times. So, for the next month we will matching all contributions of up to $100 to the following organization for Maria relief efforts in Puerto Rico:</p><p>Unidos Por Puerto Rico — <a href="http://unidosporpuertorico.com/en/">http://unidosporpuertorico.com/en/</a></p><p>To let us know about your donation either include @FormSwift in a tweet with your donation or send us an email with the receipt to jackson(at)formswift.com.</p><p>Thank you in advance for your consideration and help!</p><p>Sincerely,</p><p>All of us at FormSwift</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=35ef846ee15" width="1" height="1" alt=""><hr><p><a href="https://blog.formswift.com/helping-puerto-rico-35ef846ee15">FormSwift for Puerto Rico</a> was originally published in <a href="https://blog.formswift.com">FormSwift</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>