Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions scripts/sync-newsletter-to-resend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,16 @@ function parseNewsletterDate(dateStr: string | number | Date): Date {
/**
* Checks if a newsletter should be processed based on its date.
*/
function shouldProcessNewsletter(date: string | number | Date, slug: string): boolean {
function shouldProcessNewsletter(
date: string | number | Date,
slug: string
): boolean {
try {
const newsletterDate = parseNewsletterDate(date);
// Newsletter date must be on or after the cutoff date
const shouldProcess = isAfter(newsletterDate, CUTOFF_DATE) || isSameDay(newsletterDate, CUTOFF_DATE);
const shouldProcess =
isAfter(newsletterDate, CUTOFF_DATE) ||
isSameDay(newsletterDate, CUTOFF_DATE);

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

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

const getCloudinaryImageUrl = (publicId: string) => {
return `https://res.cloudinary.com/mikebifulco-com/image/upload/${publicId}`;
};

/**
* Gets list of changed newsletter files in current PR.
*/
Expand Down Expand Up @@ -181,6 +190,9 @@ async function main() {
React.createElement(NewsletterEmail, {
content: cleanContent,
excerpt: frontmatter.excerpt,
coverImage: frontmatter.coverImagePublicId
? getCloudinaryImageUrl(frontmatter.coverImagePublicId)
: undefined,
})
);

Expand Down
66 changes: 66 additions & 0 deletions src/data/newsletters/open-source-is-community.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
title: "Contributing to Open Source without being a Jerk"
excerpt: "Open source doesn't work without good faith - and sometimes you need to patch a dependency to do your part."
tags: [open source, javascript, react, devtools]
date: 11-11-2025
coverImagePublicId: "newsletters/open-source-is-community/cover"
slug: open-source-is-community
---

## The Big Idea

Open source works best when we treat each other like humans - not vending machines for bug fixes.

## How We Patched a Broken Package - and What It Taught Us

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).

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.

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.

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.


## Maintainers Aren't the Problem

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.

This isn't about neglect or indifference. It's about capacity.

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.


### A Better Way to Contribute

Here's what we did instead:

- 🛠 Fixed the issue locally using `pnpm patch` (need a tutorial? I [gotchu](https://mikebifulco.com/posts/patching-npm-dependencies-with-pnpm-patch))
- 💬 Shared the patch tutorial in the GitHub PR to unblock others
- 🔁 Opened a new, cleaner PR
- 🤝 Let the maintainers off the hook - no pressure, just help

This approach makes open source better for _everyone_. It gives control back to contributors, and it gives maintainers room to breathe.

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.

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.

---

## Want to Do Open Source Better?

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.

Also, if you like learning by watching smart people build in public, check out:

- [cmgriffing](https://www.twitch.tv/cmgriffing) streams dev work and product experiments on both Twitch and YouTub
- [Rizel Scarlett](https://www.twitch.tv/blackgirlbytes1) does open source work, conference prep, and interviews on her channel

---

## Give without the expectation of receiving

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.

Let's be better guests in each other's repos.
34 changes: 16 additions & 18 deletions src/utils/email/templates/EmailLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,24 +35,27 @@ export const EmailLayout = ({
<Head>
<style>{`
h1, h2, h3, h4, h5, h6 {
font-weight: 900;
color: #D83D84;
font-weight: bold !important;
color: #D83D84 !important;
margin-top: 24px;
margin-bottom: 16px;
line-height: 1.3;
}
h1 { font-size: 32px; }
h2 { font-size: 28px; }
h3 { font-size: 24px; }
h4 { font-size: 20px; }
h5 { font-size: 18px; }
h6 { font-size: 16px; }
h1 { font-size: 32px !important; }
h2 { font-size: 28px !important; }
h3 { font-size: 24px !important; }
h4 { font-size: 20px !important; }
h5 { font-size: 18px !important; }
h6 { font-size: 16px !important; }
a {
color: #D83D84;
}
`}</style>
</Head>
<Preview>{preview}</Preview>

<Tailwind>
<Body className="mx-auto my-auto bg-[#fafafa] px-4 font-sans text-xl">
<Body className="mx-auto my-auto p-4 font-sans text-xl">
<Container>
{/* Logo Section */}
<Section style={logo} align="center">
Expand Down Expand Up @@ -94,12 +97,7 @@ export const EmailLayout = ({
}}
className="mt-2 text-gray-500"
>
<Text
style={{
fontSize: 12,
}}
className="my-0"
>
<Text className="my-0 text-sm">
© {new Date().getFullYear()} &bull; 💌 Tiny Improvements &bull;{' '}
<Link
href="https://mikebifulco.com/newsletter"
Expand All @@ -109,10 +107,10 @@ export const EmailLayout = ({
</Link>{' '}
</Text>
{includeUnsubscribeLink && (
<Text className="my-0 text-xs text-gray-500">
<Text className="my-0 text-sm text-gray-500">
Not getting what you need? No worries, you can{' '}
<Link
href="{{{RESEND_UNSUBSCRIBE_LINK}}}"
href="{{{RESEND_UNSUBSCRIBE_URL}}}"
className="text-pink-600"
>
unsubscribe
Expand All @@ -139,7 +137,7 @@ const content = {
border: '1px solid rgb(0,0,0, 0.1)',
borderRadius: '3px',
overflow: 'hidden',
maxWidth: '500px',
maxWidth: '630px',
backgroundColor: '#fff',
paddingTop: '8px',
paddingBottom: '8px',
Expand Down
51 changes: 48 additions & 3 deletions src/utils/email/templates/NewsletterEmail.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import * as React from 'react';
import { Column, Markdown, Row } from '@react-email/components';
import {
Column,
Img,
Link,
Markdown,
Row,
Text,
} from '@react-email/components';

import { EmailLayout } from './EmailLayout';

type NewsletterEmailProps = {
content: string;
excerpt: string;
coverImage?: string;
};

/**
Expand All @@ -28,20 +35,58 @@ type NewsletterEmailProps = {
* ```
*/
export const NewsletterEmail = ({
content = '',
content = 'lorem ipsum dolor sit amet this is just sample content for email preview',
excerpt = 'Preview text for email clients',
coverImage = 'https://picsum.photos/1200/630',
}: NewsletterEmailProps) => {
return (
<EmailLayout
preview={excerpt}
firstName={false}
includeUnsubscribeLink={true}
>
{coverImage && (
<Row>
<Column>
<Img
src={coverImage}
alt="Newsletter cover image"
className="mt-2 w-full rounded"
style={{
maxWidth: '100%',
height: 'auto',
}}
/>
</Column>
</Row>
)}
<Row>
<Column>
<Markdown>{content}</Markdown>
</Column>
</Row>
<Row>
<Column>
<Text className="text-xl">
Give &apos;em hell out there. ✌️ <br /> - Mike
</Text>
</Column>
</Row>
<Row>
<Column>
<Text className="italic text-gray-500">
Thanks for reading 💌 Tiny Improvements. If you found this helpful,
I'd love it if you{' '}
<Link
href="https://mikebifulco.com/newsletter"
className="text-pink-600"
>
share it with a friend
</Link>
. It helps me out a great deal!
</Text>
</Column>
</Row>
</EmailLayout>
);
};
Expand Down