Merge pull request #1379 from omnivore-app/fix/newsletters

fix/newsletters
This commit is contained in:
Hongbo Wu 2022-11-02 15:43:20 +08:00 committed by GitHub
commit b7ec6ac5a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 729 additions and 6 deletions

View file

@ -27,6 +27,7 @@ import { RevueHandler } from './newsletters/revue-handler'
import { GhostHandler } from './newsletters/ghost-handler'
import { parseHTML } from 'linkedom'
import { CooperPressHandler } from './newsletters/cooper-press-handler'
import { HeyWorldHandler } from './newsletters/hey-world-handler'
const validateUrlString = (url: string) => {
const u = new URL(url)
@ -75,6 +76,7 @@ const newsletterHandlers: ContentHandler[] = [
new RevueHandler(),
new GhostHandler(),
new CooperPressHandler(),
new HeyWorldHandler(),
]
export const preHandleContent = async (

View file

@ -27,11 +27,21 @@ export class ConvertkitHandler extends ContentHandler {
dom: Document
}): Promise<boolean> {
const dom = input.dom
return Promise.resolve(
dom.querySelectorAll(
'img[src*="convertkit.com"], img[src*="convertkit-mail"]'
).length > 0
const icons = dom.querySelectorAll(
'img[src*="convertkit.com"], img[src*="convertkit-mail"]'
)
if (icons.length === 0) {
return Promise.resolve(false)
}
// ignore newsletters that have a confirmation link to the newsletter in the body
const links = dom.querySelectorAll(
'a[href*="convertkit.com"], a[href*="convertkit-mail"]'
)
const isConfirmation = Array.from(links).some((e) => {
return e.textContent === 'Confirm your subscription'
})
return Promise.resolve(!isConfirmation)
}
async parseNewsletterUrl(

View file

@ -0,0 +1,27 @@
import { ContentHandler } from '../content-handler'
export class HeyWorldHandler extends ContentHandler {
constructor() {
super()
this.name = 'hey-world'
this.senderRegex = /<.+@world.hey.com>/
}
findNewsletterHeaderHref(dom: Document): string | undefined {
const readOnline = dom.querySelectorAll('a')
let res: string | undefined = undefined
readOnline.forEach((e) => {
if (e.textContent === 'View this post online') {
res = e.getAttribute('href') || undefined
}
})
return res
}
async parseNewsletterUrl(
postHeader: string,
html: string
): Promise<string | undefined> {
return this.findNewsletterUrl(html)
}
}

View file

@ -0,0 +1,268 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style>
email-button:hover {
background-color: #0875c1;
}
</style>
</head>
<body
style="
margin: 0;
padding: 0;
-webkit-font-smoothing: antialiased;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
"
>
<div style="background-color: #efeff3">
<table
role="presentation"
cellpadding="0"
cellspacing="0"
class="email"
style="
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
background-color: #efeff3;
width: 100%;
"
bgcolor="#efeff3"
>
<tbody>
<tr>
<td style="vertical-align: top">
<div
class="email-container"
style="
margin: 0 auto;
padding: 30px 0;
padding-left: 20px;
padding-right: 20px;
max-width: 100%;
padding-top: 30px;
padding-left: 20px;
padding-bottom: 30px;
padding-right: 20px;
margin: 0 auto;
max-width: 100%;
"
>
<!--[if mso]><center><table cellpadding="0" cellspacing="0" align="center" style="" width="600" bgcolor="#ffffff"><tr><td><![endif]-->
<div
style="
background-color: #ffffff;
border-radius: 4px;
overflow: hidden;
max-width: 600;
margin: 0px auto;
"
>
<table
bgcolor="#ffffff"
cellpadding="0"
cellspacing="0"
width="100%"
style="
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
"
>
<tbody>
<tr>
<td style="vertical-align: top; padding: 20px">
<div
class="email-body"
style="background-color: #ffffff"
>
<div class="email-content">
<p
style="
margin: 1em 0;
font-size: 18px;
line-height: 1.5em;
font-family: Georgia, Times, 'Times New Roman',
serif;
color: #3d3d3d;
font-size: 16px;
line-height: 1.5;
"
>
Thanks for signing up for the
<em>Weekend Upgrade</em> newsletter! Click the
link below to confirm your subscription and
you'll be on your way.
</p>
<table
maxwidth="100"
width="100%"
style="
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
"
>
<tbody>
<tr>
<td
align="left"
style="vertical-align: top"
>
<a
class="email-button"
href="https://click.convertkit-mail2.com/d0uo2xqod0a5fm76qdtm/reh8hohqg8ogd5i2/aHR0cHM6Ly9hcHAuY29udmVydGtpdC5jb20vZm9ybXMvY29uZmlybT9rZXk9MTY0OTkzZWJlM2ViY2Y2YWMxMjFiMjU0M2M2ODEwNzUwNGRlOGY4ZSZzaWQ9NTIwOTgzNTkzNg=="
target="_blank"
rel="noopener noreferrer"
style="
color: #0875c1;
margin-bottom: 0.5em;
border: 0 none;
font-size: 16px;
line-height: 1.5;
background-color: #0875c1;
color: #ffffff;
border-radius: 4px;
color: #ffffff;
display: inline-block;
background: #d71c1c;
padding: 6px 12px;
text-align: center;
text-decoration: none;
border-radius: 4px;
margin: 6px 0;
"
url-id="1496943150"
>Confirm your subscription</a
>
</td>
</tr>
</tbody>
</table>
<p
style="
margin: 1em 0;
font-size: 18px;
line-height: 1.5em;
font-family: Georgia, Times, 'Times New Roman',
serif;
color: #3d3d3d;
font-size: 16px;
line-height: 1.5;
"
>
It's good to have you!
</p>
</div>
<div
class="email-body-footer"
style="
border-top: 1px dotted #888;
padding: 20px;
font-family: Helvetica, Arial, sans-serif;
color: #aaa;
font-size: 12px;
"
>
<p
style="
margin: 1em 0;
font-size: 18px;
line-height: 1.5em;
font-family: Georgia, Times, 'Times New Roman',
serif;
color: #3d3d3d;
font-size: 16px;
line-height: 1.5;
text-align: center;
"
>
<a
href="https://unsubscribe.convertkit-mail2.com/d0uo2xqod0a5fm76qdtm"
target="_blank"
rel="noopener noreferrer"
style="color: #0875c1"
>Unsubscribe</a
>
|
<a
href="https://preferences.convertkit-mail2.com/d0uo2xqod0a5fm76qdtm"
target="_blank"
rel="noopener noreferrer"
style="color: #0875c1"
>Update your profile</a
>
| 714 Venture Dr. #185, Morgantown, WV 26508
</p>
</div>
</div>
<div
class="built-with"
style="
display: block;
text-align: center;
margin-top: 30px;
"
>
<a
href="https://click.convertkit-mail2.com/d0uo2xqod0a5fm76qdtm/l2hehmhkgg99mnb6/aHR0cHM6Ly9jb252ZXJ0a2l0LmNvbS9mZWF0dXJlcy9lbWFpbC1tYXJrZXRpbmc_dXRtX2NhbXBhaWduPXBvd2VyZWRieSZ1dG1fY29udGVudD1lbWFpbCZ1dG1fbWVkaXVtPXJlZmVycmFsJnV0bV9zb3VyY2U9ZHluYW1pYw=="
target="_blank"
rel="noopener"
style="
color: #0875c1;
display: inline-block;
text-decoration: none;
"
url-id="1337634290"
><img
src="https://cdn.convertkit.com/assets/light-built-with-badge.png"
alt="Built with ConvertKit"
style="
display: block;
height: auto;
line-height: 100%;
outline: none;
-webkit-text-decoration: none;
text-decoration: none;
max-width: 100%;
border: 0 none;
width: 190px;
height: 36px;
"
width="190"
height="36"
/></a>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso]></td></tr></table></center><![endif]-->
</div>
</td>
<td style="vertical-align: top"></td>
</tr>
</tbody>
</table>
</div>
<!-- -->
<img
src="https://open.convertkit-mail2.com/d0uo2xqod0a5fm76qdtm"
alt=""
style="
border: 0 none;
display: block;
height: auto;
line-height: 100%;
outline: none;
-webkit-text-decoration: none;
text-decoration: none;
max-width: 100%;
"
/>
</body>
</html>

View file

@ -0,0 +1,274 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta
name="viewport"
id="viewport"
content="width=device-width,minimum-scale=1.0,maximum-scale=10.0,initial-scale=1.0"
/>
<style>
html {
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
h1 {
font-size: 1.3em;
line-height: 1.2;
margin: 0;
}
ul,
ol {
margin: 0;
padding: 0;
}
ul li,
ol li,
li li {
margin: 0 0 0 36px;
}
[dir='rtl'] li {
margin: 0 18px 0 0;
}
blockquote {
border-color: #dfdee1;
border-style: solid;
border-width: 0 0 0 1px;
margin: 0;
padding: 0 0 0 1em;
}
[dir='rtl'] blockquote,
blockquote[dir='rtl'] {
border-width: 0 1px 0 0;
padding: 0 1em 0 0;
}
pre {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo,
Courier, monospace;
font-size: 0.9em;
margin: 0;
padding: 1rem;
background-color: #f6f5f3;
white-space: pre-wrap;
word-wrap: break-word;
overflow: visible;
}
.message-content {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol';
line-height: 1.4;
}
.attachment {
display: inline-block;
margin: 0;
padding: 0;
}
.attachment__caption {
padding: 0;
text-align: center;
}
.attachment__caption a[href] {
text-decoration: none;
color: #333;
}
.attachment--preview {
width: 100%;
text-align: center;
margin: 0.625em 0;
}
.attachment--preview img {
border: 1px solid #dfdee1;
vertical-align: middle;
width: auto;
max-width: 100%;
max-height: 640px;
}
.attachment--preview .attachment__caption {
color: #716d7b;
font-size: 0.85em;
margin-top: 0.625em;
}
.attachment--file {
color: #282138;
line-height: 1;
margin: 0 2px 2px 0;
padding: 0.4em 1em;
border: 1px solid #dfdee1;
border-radius: 5px;
}
.permalink {
color: inherit;
}
.txt--xx-small {
font-size: 14px;
}
.flush {
margin: 0;
padding: 0;
}
.push--bottom {
margin-bottom: 8px;
}
.border--top {
border-top: 1px solid #ece9e6;
}
.btn {
padding: 0.2em 0.4em;
font-weight: 500;
text-decoration: none;
border-radius: 3rem;
white-space: nowrap;
background: #5522fa;
border-color: #5522fa;
color: #fff;
}
.btn--email {
display: inline-block;
text-align: center;
font-weight: 500;
font-size: 1em;
text-decoration: none;
border-radius: 2em;
white-space: nowrap;
background: #5522fa;
border-color: #5522fa;
color: #fff;
border-top: 0.3em solid #5522fa;
border-left: 1em solid #5522fa;
border-bottom: 0.3em solid #5522fa;
border-right: 1em solid #5522fa;
}
.shaded {
padding: 1em;
border-radius: 4px;
background-color: #f6f5f3;
border: 1px solid #dfdee1;
}
</style>
</head>
<body>
<div class="message-content">
<div class="trix-content">
<div>
With Twitter under new ownership, it's a great time for us to imagine
how this hellscape might actually get fixed. And by fixed, I mean be a
place where people enjoy hanging out, where the debate over content
moderation is settled, and where Elon Musk might make his money back.
Easy peasy then!<br /><br />To start, here's my fundamental premise:
The internet is full of the bland, the wonderful, the obscure, the
sacrilegious, the offensive, the infuriating, and the nasty. The
internet, as a whole, is governed by local laws on speech, not by a
council of global content moderators, not by single individuals. This
is a feature not a bug.<br /><br />An American can sue another
American under US defamation laws for stuff posted on the internet.
But the Thai government won't get an American extradited under its
lèse-majesté laws if that person offends the country's monarch on the
internet. Restrictions on speech are based on jurisdictions (and
Twitter is thankfully liable in one of most liberal speech
jurisdictions around). This is also a feature not a bug.<br /><br />Twitter
should embrace these features, and accept its role as a fundamental
protocol. Just like HTTP, just like SMTP, just like FTP. All protocols
that can be used to transmit all sorts of disagreeable content, but
where the moderation of said content is left to individuals, not some
all-knowing overlords.<br /><br />This isn't possible today because
Twitter shoves so much speech in front of you that you never asked to
see. It allows strangers to invade popular threads with no protections
available to the original author.<br /><br />You know that you can
find the most vile, nasty shit on the internet if you search for the
right words on Google. So most people just don't do that! They manage
their own exposure by moderating their own behavior. This is again a
feature, not a bug.<br /><br />This could be Twitter!<br /><br />If
Twitter stopped shoving all this speech you never asked to see in
front of you, there would be no need for an army of content
moderators. The internet at large is not filtered through such an
army, and it's all the better for it. Governments keep trying to
impose such a filter, but so far only the most authoritarian regimes
have succeeded (and up until a few minutes ago, there was broad
consensus in the Western world that this was bad).<br /><br />This
would mean that Twitter would have to change in a few fundamental
ways:<br /><br />
</div>
<ol>
<li>
Only speech from people you've asked to follow should appear in your
feed. If any of those people post something disagreeable to your
tastes, you can moderate that content by unfollowing them.
</li>
<li>
Authors should be deputized as content moderators of their own
posts. Just like someone hosting a blog with comments on the
internet is. You can either say you don't want comments at all, that
comments need to be pre-approved, that you'll remove the bad ones,
or that you'll just let it flow.
</li>
<li>
All the promoted tweets that Twitter wants to showcase to the world
should pass through a positive rather than negative editorial
process. Meaning Twitter employees pick what they like to feature
rather than ban what they don't.
</li>
</ol>
<div>
<br />Thus, like the internet at large, everyone gets to control the
kind of speech they're exposed to. Different people will come to
different conclusions on the kind speech they wish to be exposed to.
This is the fundamental wisdom of the infamous Section 230 protection
that has allowed the internet as we know it to exist.<br /><br />This
curation should be something you can outsource to trusted delegates.
Let me experience Twitter as a list of authors and threads maintained
by someone I trust.<br /><br />It'll also mean, like the internet at
large, that you'll be able to find Bad Words, Terrible Words, No Good
Words, if you deliberately seek them out. Twitter will serve as a
portal to this in the same way Google does. These words may be hurtful
or harmful.<br /><br />But all the content moderators in the world
couldn't put the utopia that a platform for hundreds of millions won't
contain disagreeable speech together again. <br /><br />Accept that
reality. Accept that the price we pay for an open, free internet is
one which permits the darker sides of humanity. Lean on the local
jurisdictions and laws, democratically controlled as they are, to
police this reality. Like we've done with that beautiful bastard that
is the internet.<br /><br />Let Twitter be free to ascend to sit
amongst all the other fundamental protocols that constitutes the
pantheon of the internet.<br /><br />You can do it, Mr Musk!
</div>
</div>
<br />
<hr class="border--top" />
<p class="txt--xx-small flush push--bottom">
<a
class="permalink"
href="https://world.hey.com/dhh/here-s-how-to-fix-twitter-79632ecb"
>View this post online</a
>
</p>
<p class="txt--xx-small flush push--bottom">
Youre subscribed to
<a class="permalink" href="https://world.hey.com/dhh"
>David Heinemeier Hansson</a
>
on
<a class="permalink" href="https://hey.com/world?source=hw-email"
>HEY World</a
>
</p>
<p class="txt--xx-small flush push--bottom">
Dont want these posts anymore?
<a
class="permalink"
href="https://world.hey.com/dhh/subscribers/MtuoW9TvSJK9o5c7ohB72V2s/unsubscribe"
>Unsubscribe</a
>
</p>
<p class="txt--xx-small flush push--bottom">
HEY: PO Box 803338, PMB 53289, Chicago, Illinois 60680
</p>
</div>
</body>
</html>

View file

@ -0,0 +1,88 @@
<div dir="ltr"><br><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">---------- Forwarded message ---------<br>From: <strong class="gmail_sendername" dir="auto">Tomasz Tunguz</strong> <span dir="auto">&lt;<a href="mailto:blog@tomtunguz.com">blog@tomtunguz.com</a>&gt;</span><br>Date: Fri, 28 Oct 2022 at 19:15<br>Subject: Grey Skies in Cloud Earnings<br>To: &lt;<a href="mailto:martin@doversten.se">martin@doversten.se</a>&gt;<br></div><br><br><div class="msg5509635934175298552"><u></u>
<div style="margin:0;padding:0">
<div style="background-color:#aaaaaa"><table role="presentation" cellpadding="0" cellspacing="0" style="border-collapse:separate;background-color:#ffffff;width:100%" bgcolor="#aaaaaa"><tbody><tr>
<td style="vertical-align:top"><div style="margin:0 auto;padding:45px 0 30px;padding-left:20px;padding-right:20px;max-width:600px;padding-top:30px;padding-left:20px;padding-bottom:30px;padding-right:20px;margin:0 auto;max-width:600px">
<div style="border-left:10px;border-style:solid;border-top:none;border-right:none;border-bottom:none;padding:3px 20px 1px 20px;margin:0 0 15px 0;border-color:#6495ed">
<h1 style="font-weight:bold;font-style:normal;font-size:1em;margin:0;font-size:2em;font-weight:700;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#281842;line-height:1.5">Tomasz Tunguz</h1>
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5;text-align:left">Venture Capitalist at Redpoint</p>
</div>
<div><div style="padding:20px 30px">
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5"><span style="color:#ababab"><em>If you were forwarded this newsletter, and you&#39;d like to receive it in the future, </em></span><a href="https://click.convertkit-mail2.com/68ugeo9lmxa8hkwqe7dbo/dpheh0hql2pz8zam/aHR0cHM6Ly90dHVuZ3V6LmNrLnBhZ2UvYjcxZmQ5YTNkZA==" rel="noopener noreferrer" style="font-weight:bold;color:#000000" target="_blank"><span style="color:#ababab"><em>subscribe here.</em></span></a><span style="color:#ababab"><em></em></span></p>
<h1 style="font-weight:bold;font-style:normal;font-size:1em;margin:0;font-size:2em;font-weight:700;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#281842;line-height:1.5"><a href="https://click.convertkit-mail2.com/68ugeo9lmxa8hkwqe7dbo/e0hph7hk8dr9qpu8/aHR0cHM6Ly93d3cudG9tdHVuZ3V6LmNvbS9xMzIwMjItY2xvdWQtZWFybmluZ3Mv" rel="noopener noreferrer" style="font-weight:bold;color:#000000" target="_blank">Grey Skies in Cloud Earnings</a></h1>
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5">
</p>
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5">Im watching <a href="https://click.convertkit-mail2.com/68ugeo9lmxa8hkwqe7dbo/7qh7h8h0rxnvw6iz/aHR0cHM6Ly90b210dW5ndXouY29tL3RoZS1pbmRleC1vZi1zb2Z0d2FyZS1idXllcnMv" style="font-weight:bold;color:#000000" target="_blank">public company earnings to identify early weaknesses in the software market</a>. This week Microsoft, Google/ Alphabet, &amp; Amazon reported their third quarter figures.</p>
<table style="border-collapse:separate">
<thead>
<tr>
<th>Company</th>
<th style="text-align:right">Q-4 CAGR</th>
<th style="text-align:right">Q-4 CAGR</th>
<th style="text-align:right">Q-3 CAGR</th>
<th style="text-align:right">Q-2 CAGR</th>
<th style="text-align:right">Q-1 CAGR</th>
<th style="text-align:right">Q0 CAGR</th>
</tr>
</thead>
<tbody>
<tr>
<td style="vertical-align:top">Microsoft Azure</td>
<td style="vertical-align:top;text-align:right">50%</td>
<td style="vertical-align:top;text-align:right">51%</td>
<td style="vertical-align:top;text-align:right">46%</td>
<td style="vertical-align:top;text-align:right">46%</td>
<td style="vertical-align:top;text-align:right">40%</td>
<td style="vertical-align:top;text-align:right">35%</td>
</tr>
<tr>
<td style="vertical-align:top">Google Cloud Platform</td>
<td style="vertical-align:top;text-align:right">46%</td>
<td style="vertical-align:top;text-align:right">54%</td>
<td style="vertical-align:top;text-align:right">45%</td>
<td style="vertical-align:top;text-align:right">51%</td>
<td style="vertical-align:top;text-align:right">35%</td>
<td style="vertical-align:top;text-align:right">38%</td>
</tr>
<tr>
<td style="vertical-align:top">Amazon Web Services</td>
<td style="vertical-align:top;text-align:right">37%</td>
<td style="vertical-align:top;text-align:right">39%</td>
<td style="vertical-align:top;text-align:right">40%</td>
<td style="vertical-align:top;text-align:right">40%</td>
<td style="vertical-align:top;text-align:right">33%</td>
<td style="vertical-align:top;text-align:right">27%</td>
</tr>
</tbody>
</table>
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5">The meaningful decline in growth rates started four quarters ago is hard to ignore.</p>
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5"><img src="https://res.cloudinary.com/dzawgnnlr/image/upload/w_700,c_scale/co68b8xfjofqhgyw2ome.png" alt="image" style="border:0 none;display:block;height:auto;line-height:100%;outline:none;text-decoration:none;max-width:100%">
Lets put these figures into context. Infrastructure revenue growth averaged 33% this quarter, which is astounding considering were talking about businesses that sum to more than $50b of revenue per quarter.</p>
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5">Customers spent $12b more this quarter - an astounding figure. At a 7x multiple of revenue, that is another $84b of market cap creation, in theory.</p>
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5">But, the change in slope is meaningful, too. A year ago, these business units grew at 44% annually. The current recession is evident in the charts: growth rates have declined by 25% in six months time.</p>
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5">The kink downwards in the red line at Q-2 shows a sudden deceleration in AWS growth rate. GCP is more volatile, likely driven by the volatility of bigger deals closing on a smaller revenue base. Meanwhile, Azure has declined in a more steady cadence.</p>
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5">The data suggests a broader slowdown in software spending as these companies are <a href="https://click.convertkit-mail2.com/68ugeo9lmxa8hkwqe7dbo/7qh7h8h0rxnvw6iz/aHR0cHM6Ly90b210dW5ndXouY29tL3RoZS1pbmRleC1vZi1zb2Z0d2FyZS1idXllcnMv" style="font-weight:bold;color:#000000" target="_blank">indices of software buyers behavior</a>.</p>
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5">Public software multiples - <a href="https://click.convertkit-mail2.com/68ugeo9lmxa8hkwqe7dbo/z2hghnhoqml9prap/aHR0cHM6Ly90b210dW5ndXouY29tL3JhdGVzLWFuZC1tdWx0aXBsZXMtMjAyMi8=" style="font-weight:bold;color:#000000" target="_blank">4.9x as of last week</a>, not far from the decade low of 3.3x - should depress further given these results &amp; forward guidance. Microsoft stock is down 10%; Amazon fell about 20%; Google about 12%.</p>
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5">Lopping another 15% off multiples implies the median software company should trade at 4.2x forward revenues, a far cry from the 15x we observed not twelve months ago.</p>
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5"></p>
</div></div>
<div style="margin:8px 0 5px;padding:0 0 0 30px"><span style="display:inline-block;border-top-style:solid;border-top-width:5px;width:40px;border-color:#6495ed"></span></div>
<div style="font-size:14px;padding:20px 30px;font-family:Helvetica,Arial,sans-serif">
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5;font-size:14px;color:#868686;text-align:left">If you didnt enjoy the email you can <a href="https://unsubscribe.convertkit-mail2.com/68ugeo9lmxa8hkwqe7dbo" rel="noopener noreferrer" style="font-weight:bold;color:#000000" target="_blank">unsubscribe here</a>. </p>
<p style="margin:1em 0;font-size:18px;line-height:1.5em;font-family:Arial,-apple-system,BlinkMacSystemFont,&#39;Segoe UI&#39;,Roboto,Oxygen-Sans,Ubuntu,Cantarell,&#39;Helvetica Neue&#39;,sans-serif;color:#4e4e4e;font-size:16px;line-height:1.5;font-size:14px;color:#868686;text-align:left">2969 Woodside Road, Woodside, CA 94062</p>
</div>
</div></td>
<td style="vertical-align:top"></td>
</tr></tbody></table></div>
<img src="https://open.convertkit-mail2.com/68ugeo9lmxa8hkwqe7dbo" alt="" style="border:0 none;display:block;height:auto;line-height:100%;outline:none;text-decoration:none;max-width:100%">
</div>
</div></div></div>

View file

@ -17,6 +17,7 @@ import { GhostHandler } from '../src/newsletters/ghost-handler'
import { CooperPressHandler } from '../src/newsletters/cooper-press-handler'
import { getNewsletterHandler } from '../src'
import { parseHTML } from 'linkedom'
import { HeyWorldHandler } from '../src/newsletters/hey-world-handler'
chai.use(chaiAsPromised)
chai.use(chaiString)
@ -144,7 +145,8 @@ describe('Newsletter email test', () => {
})
it('fixes up static tweets in Substack newsletters', async () => {
const url = 'https://astralcodexten.substack.com/p/nick-cammarata-on-jhana'
const url =
'https://astralcodexten.substack.com/p/nick-cammarata-on-jhana'
const html = load(
'./test/data/substack-with-static-tweets-newsletter.html'
)
@ -160,7 +162,9 @@ describe('Newsletter email test', () => {
expect(handler?.shouldPreParse(url, dom)).to.be.true
const preparsed = await handler?.preParse(url, dom)
const tweets = Array.from(preparsed?.querySelectorAll('div[class="_omnivore-static-tweet"]') ?? [])
const tweets = Array.from(
preparsed?.querySelectorAll('div[class="_omnivore-static-tweet"]') ?? []
)
expect(tweets.length).to.eq(7)
})
@ -219,6 +223,40 @@ describe('Newsletter email test', () => {
})
expect(handler).to.be.instanceOf(CooperPressHandler)
})
it('returns HeyWorldHandler for hey world newsletter', async () => {
const html = load('./test/data/hey-world-newsletter.html')
const handler = await getNewsletterHandler({
html,
postHeader: '',
from: 'Hongbo Wu <hw@world.hey.com>',
unSubHeader:
'<https://world.hey.com/dhh/subscribers/MtuoW9TvSJK9o5c7ohB72V2s/unsubscribe>',
})
expect(handler).to.be.instanceOf(HeyWorldHandler)
})
it('returns ConvertkitHandler for Tomasz Tunguz newsletter', async () => {
const html = load('./test/data/tomasz-tunguz-newsletter.html')
const handler = await getNewsletterHandler({
html,
postHeader: '',
from: '',
unSubHeader: '',
})
expect(handler).to.be.instanceOf(ConvertkitHandler)
})
it('returns undefined for convertkit confirmation email', async () => {
const html = load('./test/data/convertkit-confirmation.html')
const handler = await getNewsletterHandler({
html,
postHeader: '',
from: '',
unSubHeader: '',
})
expect(handler).to.be.undefined
})
})
describe('findNewsletterUrl', async () => {
@ -334,6 +372,22 @@ describe('Newsletter email test', () => {
expect(url).to.startWith('https://nodeweekly.com/issues/459')
})
})
context('when email is from hey world', () => {
before(() => {
nock('https://world.hey.com')
.head('/dhh/here-s-how-to-fix-twitter-79632ecb')
.reply(200, '')
})
it('gets the URL from the header', async () => {
const html = load('./test/data/hey-world-newsletter.html')
const url = await new HeyWorldHandler().findNewsletterUrl(html)
expect(url).to.startWith(
'https://world.hey.com/dhh/here-s-how-to-fix-twitter-79632ecb'
)
})
})
})
describe('generateUniqueUrl', () => {