Skip to content

Commit cb936f0

Browse files
authored
Merge pull request #1110 from mbifulco/feat/newsletter-on-oss
feat: newsletter on community
2 parents 5504d6c + 7ab71ad commit cb936f0

File tree

4 files changed

+145
-24
lines changed

4 files changed

+145
-24
lines changed

scripts/sync-newsletter-to-resend.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,16 @@ function parseNewsletterDate(dateStr: string | number | Date): Date {
7171
/**
7272
* Checks if a newsletter should be processed based on its date.
7373
*/
74-
function shouldProcessNewsletter(date: string | number | Date, slug: string): boolean {
74+
function shouldProcessNewsletter(
75+
date: string | number | Date,
76+
slug: string
77+
): boolean {
7578
try {
7679
const newsletterDate = parseNewsletterDate(date);
7780
// Newsletter date must be on or after the cutoff date
78-
const shouldProcess = isAfter(newsletterDate, CUTOFF_DATE) || isSameDay(newsletterDate, CUTOFF_DATE);
81+
const shouldProcess =
82+
isAfter(newsletterDate, CUTOFF_DATE) ||
83+
isSameDay(newsletterDate, CUTOFF_DATE);
7984

8085
if (!shouldProcess) {
8186
console.log(`⏭️ Skipping ${slug}: dated before cutoff (${date})`);
@@ -97,11 +102,15 @@ function convertImagesToHtml(content: string): string {
97102
const imageRegex = /<Image\s+publicId="([^"]+)"[^>]*\/>/g;
98103

99104
return content.replace(imageRegex, (_, publicId) => {
100-
const cloudinaryUrl = `https://res.cloudinary.com/mikebifulco-com/image/upload/${publicId}`;
105+
const cloudinaryUrl = getCloudinaryImageUrl(publicId);
101106
return `![](${cloudinaryUrl})`;
102107
});
103108
}
104109

110+
const getCloudinaryImageUrl = (publicId: string) => {
111+
return `https://res.cloudinary.com/mikebifulco-com/image/upload/${publicId}`;
112+
};
113+
105114
/**
106115
* Gets list of changed newsletter files in current PR.
107116
*/
@@ -181,6 +190,9 @@ async function main() {
181190
React.createElement(NewsletterEmail, {
182191
content: cleanContent,
183192
excerpt: frontmatter.excerpt,
193+
coverImage: frontmatter.coverImagePublicId
194+
? getCloudinaryImageUrl(frontmatter.coverImagePublicId)
195+
: undefined,
184196
})
185197
);
186198

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
title: "Contributing to Open Source without being a Jerk"
3+
excerpt: "Open source doesn't work without good faith - and sometimes you need to patch a dependency to do your part."
4+
tags: [open source, javascript, react, devtools]
5+
date: 11-11-2025
6+
coverImagePublicId: "newsletters/open-source-is-community/cover"
7+
slug: open-source-is-community
8+
---
9+
10+
## The Big Idea
11+
12+
Open source works best when we treat each other like humans - not vending machines for bug fixes.
13+
14+
## How We Patched a Broken Package - and What It Taught Us
15+
16+
Recently at [Craftwork](https://craftwork.com), we hit a snag using the [@payloadcms/storage-uploadthing](https://github.com/payloadcms/storage-uploadthing) plugin for image uploads in [Payload CMS](https://payloadcms.com).
17+
18+
One of our engineers dug in, found the issue, and fixed it. We opened a [pull request](https://github.com/payloadcms/payload/pull/14250), hoping to help others in the same situation.
19+
20+
But here's the thing: We couldn't afford to wait for the PR to get merged before moving forward. Publishing a forked npm package is a common fallback - but it can be hard to maintain long-term.
21+
22+
In addition to creating a fork to submit a fix, we used [`pnpm patch`](https://mikebifulco.com/posts/patching-npm-dependencies-with-pnpm-patch) to apply the fix to our repo. It's clean, version-controlled, and works until the upstream package is updated.
23+
24+
25+
## Maintainers Aren't the Problem
26+
27+
It's easy to forget that open source projects are often maintained by small teams under heavy load. At the time of writing, Payload has over 375 open pull requests. Expecting our fix to jump the line would be absurd.
28+
29+
This isn't about neglect or indifference. It's about capacity.
30+
31+
In other repos, we've seen PRs sit untouched for months, with long threads of angsty, impatient comments. I get it - but that doesn't help anyone. Especially not the maintainers.
32+
33+
34+
### A Better Way to Contribute
35+
36+
Here's what we did instead:
37+
38+
- 🛠 Fixed the issue locally using `pnpm patch` (need a tutorial? I [gotchu](https://mikebifulco.com/posts/patching-npm-dependencies-with-pnpm-patch))
39+
- 💬 Shared the patch tutorial in the GitHub PR to unblock others
40+
- 🔁 Opened a new, cleaner PR
41+
- 🤝 Let the maintainers off the hook - no pressure, just help
42+
43+
This approach makes open source better for _everyone_. It gives control back to contributors, and it gives maintainers room to breathe.
44+
45+
It is easy to forget that contributing to open source isn't just Pull Requests - discussion, community building, managing expectations, and creative solutions help us all build a better internet together.
46+
47+
I'm confident you'll get more results if you treat people with humanity - and you will [build a reputation for yourself](https://mikebifulco.com/newsletter/serendipity-isnt-an-accident) that makes people eager to help you when they can.
48+
49+
---
50+
51+
## Want to Do Open Source Better?
52+
53+
Read [*Working in Public*](https://hardcover.app/books/working-in-public) by Nadia Eghbal. It's the best book I've read on the culture of open source: what's broken, what's beautiful, and what's worth fixing.
54+
55+
Also, if you like learning by watching smart people build in public, check out:
56+
57+
- [cmgriffing](https://www.twitch.tv/cmgriffing) streams dev work and product experiments on both Twitch and YouTub
58+
- [Rizel Scarlett](https://www.twitch.tv/blackgirlbytes1) does open source work, conference prep, and interviews on her channel
59+
60+
---
61+
62+
## Give without the expectation of receiving
63+
64+
Open source is a special kind of economy. Show up with something useful. Share what you've learned. Make it easier for the next person.
65+
66+
Let's be better guests in each other's repos.

src/utils/email/templates/EmailLayout.tsx

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,27 @@ export const EmailLayout = ({
3535
<Head>
3636
<style>{`
3737
h1, h2, h3, h4, h5, h6 {
38-
font-weight: 900;
39-
color: #D83D84;
38+
font-weight: bold !important;
39+
color: #D83D84 !important;
4040
margin-top: 24px;
4141
margin-bottom: 16px;
4242
line-height: 1.3;
4343
}
44-
h1 { font-size: 32px; }
45-
h2 { font-size: 28px; }
46-
h3 { font-size: 24px; }
47-
h4 { font-size: 20px; }
48-
h5 { font-size: 18px; }
49-
h6 { font-size: 16px; }
44+
h1 { font-size: 32px !important; }
45+
h2 { font-size: 28px !important; }
46+
h3 { font-size: 24px !important; }
47+
h4 { font-size: 20px !important; }
48+
h5 { font-size: 18px !important; }
49+
h6 { font-size: 16px !important; }
50+
a {
51+
color: #D83D84;
52+
}
5053
`}</style>
5154
</Head>
5255
<Preview>{preview}</Preview>
5356

5457
<Tailwind>
55-
<Body className="mx-auto my-auto bg-[#fafafa] px-4 font-sans text-xl">
58+
<Body className="mx-auto my-auto p-4 font-sans text-xl">
5659
<Container>
5760
{/* Logo Section */}
5861
<Section style={logo} align="center">
@@ -94,12 +97,7 @@ export const EmailLayout = ({
9497
}}
9598
className="mt-2 text-gray-500"
9699
>
97-
<Text
98-
style={{
99-
fontSize: 12,
100-
}}
101-
className="my-0"
102-
>
100+
<Text className="my-0 text-sm">
103101
© {new Date().getFullYear()} &bull; 💌 Tiny Improvements &bull;{' '}
104102
<Link
105103
href="https://mikebifulco.com/newsletter"
@@ -109,10 +107,10 @@ export const EmailLayout = ({
109107
</Link>{' '}
110108
</Text>
111109
{includeUnsubscribeLink && (
112-
<Text className="my-0 text-xs text-gray-500">
110+
<Text className="my-0 text-sm text-gray-500">
113111
Not getting what you need? No worries, you can{' '}
114112
<Link
115-
href="{{{RESEND_UNSUBSCRIBE_LINK}}}"
113+
href="{{{RESEND_UNSUBSCRIBE_URL}}}"
116114
className="text-pink-600"
117115
>
118116
unsubscribe
@@ -139,7 +137,7 @@ const content = {
139137
border: '1px solid rgb(0,0,0, 0.1)',
140138
borderRadius: '3px',
141139
overflow: 'hidden',
142-
maxWidth: '500px',
140+
maxWidth: '630px',
143141
backgroundColor: '#fff',
144142
paddingTop: '8px',
145143
paddingBottom: '8px',

src/utils/email/templates/NewsletterEmail.tsx

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
import * as React from 'react';
2-
import { Column, Markdown, Row } from '@react-email/components';
1+
import {
2+
Column,
3+
Img,
4+
Link,
5+
Markdown,
6+
Row,
7+
Text,
8+
} from '@react-email/components';
39

410
import { EmailLayout } from './EmailLayout';
511

612
type NewsletterEmailProps = {
713
content: string;
814
excerpt: string;
15+
coverImage?: string;
916
};
1017

1118
/**
@@ -28,20 +35,58 @@ type NewsletterEmailProps = {
2835
* ```
2936
*/
3037
export const NewsletterEmail = ({
31-
content = '',
38+
content = 'lorem ipsum dolor sit amet this is just sample content for email preview',
3239
excerpt = 'Preview text for email clients',
40+
coverImage = 'https://picsum.photos/1200/630',
3341
}: NewsletterEmailProps) => {
3442
return (
3543
<EmailLayout
3644
preview={excerpt}
3745
firstName={false}
3846
includeUnsubscribeLink={true}
3947
>
48+
{coverImage && (
49+
<Row>
50+
<Column>
51+
<Img
52+
src={coverImage}
53+
alt="Newsletter cover image"
54+
className="mt-2 w-full rounded"
55+
style={{
56+
maxWidth: '100%',
57+
height: 'auto',
58+
}}
59+
/>
60+
</Column>
61+
</Row>
62+
)}
4063
<Row>
4164
<Column>
4265
<Markdown>{content}</Markdown>
4366
</Column>
4467
</Row>
68+
<Row>
69+
<Column>
70+
<Text className="text-xl">
71+
Give &apos;em hell out there. ✌️ <br /> - Mike
72+
</Text>
73+
</Column>
74+
</Row>
75+
<Row>
76+
<Column>
77+
<Text className="italic text-gray-500">
78+
Thanks for reading 💌 Tiny Improvements. If you found this helpful,
79+
I'd love it if you{' '}
80+
<Link
81+
href="https://mikebifulco.com/newsletter"
82+
className="text-pink-600"
83+
>
84+
share it with a friend
85+
</Link>
86+
. It helps me out a great deal!
87+
</Text>
88+
</Column>
89+
</Row>
4590
</EmailLayout>
4691
);
4792
};

0 commit comments

Comments
 (0)