npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

spmailer

v0.2.4

Published

Send targeted "action-needed" emails

Downloads

9

Readme

Spmailer

Send targeted "action-needed" emails

Sometimes, your squad needs to do a large-scale migration with the collaboration of downstream squads. So, you build up a nice spreadsheet of where work is needed, then you notify the downstreams with a polite link to the spreadsheet.

Unfortunately, this puts a bit of a burden on the downstream squads:

  • They need to search through a potentially huge spreadsheet to find out which specific "things" this email is about
  • If they're a member of multiple groups, they need to look through the spreadsheet for each one
  • Due to this added effort, it's harder for downstreams to triage how much effort such an email entails, and they're more likely to defer it.

Instead, this tool should make it easier to submit "personalized" emails for each affected group, so they can see at a glance what work needs to be done.

Usage

First, generate a Google App Password so that emails can be sent from your account:

  1. Go to the Google App Passwords page
  2. Click the Select app dropdown and choose "Other", write spmailer into the provided input field
  3. Click GENERATE
  4. In your terminal, export two environment variables:
  • export GMAIL_USERNAME=<[email protected]>
  • export GMAIL_APP_PASSWORD=<the 16 characters generated from the above steps>

Create an email template file. Put your subject first, followed by a line with -----:

You have {{ rows | length }} components that depend on the soon-deprecated "foo" service
-----
Hello {{ recipient }}

We will be deprecating the glorious "foo" service soon.
We are telling you this because you own components that still depend on "foo", and should migrate to "bar" instead.

{% for row in rows -%}
* https://backstage.spotify.net/components/{{ row.componentId }}
{% endfor -%}

Thank you for your understanding in these trying times,
The "foo" fighters

Next, formulate your data of "teams to email" into a CSV file:

recipient,componentId
[email protected],service-1
[email protected],service-2
[email protected],service-3

Finally, run spmailer:

npx spmailer foo.template foo.csv
Sent 2 emails!

To test your emails before submission, you can run the following and look at the files in the created spmailer-dry-run folder:

npx spmailer foo.template foo.csv --dry-run

Templating

The template is rendered with Mozilla Nunjucks. The context (variables) provided to the template take the following form:

{
  "recipient": "<[email protected]>",
  "<groupedVariable>": "<groupedVariableValue>"
  "...": "...",
  "rows": [
    {
      "<rowVariable>": "<rowVariableValue>",
      "...": "..."
    }
  ]
}

The variables provided are based on the columns in your CSV file. "Grouped" variables are always the same for a recipient, while "row" variables are one-to-one with row fields in the CSV spreadsheet. You can designate the difference between "grouped" and "ungrouped" variables using the ENDGROUP column name.

For example:

recipient,team,city,ENDGROUP,componentId,repository
[email protected],A team,Stockholm,,sysmodel,https://ghe.spotify.net/tools/sysmodel
[email protected],A team,Stockholm,,backstage-frontend,https://ghe.spotify.net/backstage/backstage-frontend/
[email protected],Bees,Madrid,,backstage-backend,https://ghe.spotify.net/backstage/backstage-backend
[email protected],Cruisin,New Yawk,,jarvis,https://ghe.spotify.net/tools/jarvis
[email protected],Cruisin,New Yawk,,dashd,https://ghe.spotify.net/tools/dashd

This will be available to the template as:

{
  "recipient": "[email protected]",
  "team": "A team",
  "city": "Stockholm",
  "rows": [
    {
      "componentId": "sysmodel",
      "repository": "https://ghe.spotify.net/tools/sysmodel"
    },
    {
      "componentId": "backstage-frontend",
      "repository": "https://ghe.spotify.net/backstage/backstage-frontend"
    }
  ]
}
{
  "recipient": "[email protected]",
  "team": "Bees",
  "city": "Madrid",
  "rows": [
    {
      "componentId": "backstage-backend",
      "repository": "https://ghe.spotify.net/backstage/backstage-backend"
    }
  ]
}
{
  "recipient": "[email protected]",
  "team": "Cruisin",
  "city": "New Yawk",
  "rows": [
    {
      "componentId": "jarvis",
      "repository": "https://ghe.spotify.net/tools/jarvis"
    },
    {
      "componentId": "dashd",
      "repository": "https://ghe.spotify.net/tools/dashd"
    }
  ]
}

Which could then be used in a template like:

Be advised, you own some components
-----
Hello {{ team }} from {{ city }}, you own the following things:

{% for row in rows -%}
* {{ row.componentId }} at {{ row.repository }}
{% endfor -%}

Which would be rendered into three emails. The one to [email protected] would have the subject Be advised, you own some components, and the body would look like:

Hello Cruisin from New Yawk, you own the following things:

* jarvis at https://ghe.spotify.net/tools/jarvis
* dashd at https://ghe.spotify.net/tools/dashd