Merge branch 'feat/refresh' into docs/minor-readme-updatess

This commit is contained in:
Lucas Smith
2023-09-30 00:48:30 +10:00
committed by GitHub
6 changed files with 21 additions and 150 deletions

View File

@@ -1,10 +1,10 @@
---
title: The Early Adopters Plan
description: We are still early. Early enough for you to get a sweet deal for supporting Documenso's Mission. Join the movement and get a shiny early adopter account in the process.
description: Launch Week Day 4 and we are still early! Early enough for you to get a sweet deal for supporting Documenso's Mission. Join the movement and get a shiny early adopter account in the process.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2023-09-20
date: 2023-09-28
tags:
- Paid Plan
- Metrics
@@ -24,6 +24,8 @@ tags:
</figcaption>
</figure>
> TLDR; we have 100 early adopter accounts available at a great price. [Secure yours now](https://documenso.com/pricing).
## Community-Driven Development
As we ramp up hiring and development speed for Documenso, I want to discuss how we plan to build its core version.
@@ -48,7 +50,7 @@ Documenso currently runs the community reviewed 0.9.1 version. Getting from here
## Extending our open metrics
As part of our ongoing effort to be open and transparent in our doing, we are adding "Early Adopters" to our [/open page](https://documenso.com/open) page. After we exceed the early adopter slots, this metric will transition to "Customers". When no more early adopter seats can be claimed, the early adopter plan will transition to a standard paid plan. It will still be priced at $30/mo., but will no longer include upcoming features or unlimited seats.
As part of our ongoing effort to be open and transparent in our doing, we are adding "Early Adopters" to our [/open page](https://documenso.com/open) page. After we exceed the early adopter slots, this metric will transition to "Customers". When no more early adopter seats can be claimed, the early adopter plan will transition to a standard paid plan. It will still be priced at $30/mo., but will no longer include upcoming features or unlimited seats. You can [claim your early adopter account here](https://documenso.com/pricing).
If you have any questions or comments, please reach out on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord).

12
package-lock.json generated
View File

@@ -1868,6 +1868,17 @@
"resolved": "apps/marketing",
"link": true
},
"node_modules/@documenso/nodemailer-resend": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@documenso/nodemailer-resend/-/nodemailer-resend-1.0.0.tgz",
"integrity": "sha512-rG+jBbBEsVJUBU6v/2hb+OQD1m3Lhn49TOzQjln73zzL1B/sZsHhYOKpNPlTX0/FafCP7P9fKerndEeIKn54Vw==",
"dependencies": {
"resend": "^1.1.0"
},
"peerDependencies": {
"nodemailer": "^6.9.3"
}
},
"node_modules/@documenso/prettier-config": {
"resolved": "packages/prettier-config",
"link": true
@@ -19794,6 +19805,7 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@documenso/nodemailer-resend": "1.0.0",
"@react-email/components": "^0.0.7",
"nodemailer": "^6.9.3",
"react-email": "^1.9.4",

View File

@@ -1,7 +1,8 @@
import { createTransport } from 'nodemailer';
import { ResendTransport } from '@documenso/nodemailer-resend';
import { MailChannelsTransport } from './transports/mailchannels';
import { ResendTransport } from './transports/resend';
const getTransport = () => {
const transport = process.env.NEXT_PRIVATE_SMTP_TRANSPORT ?? 'smtp-auth';

View File

@@ -17,6 +17,7 @@
"worker:test": "tsup worker/index.ts --format esm"
},
"dependencies": {
"@documenso/nodemailer-resend": "1.0.0",
"@react-email/components": "^0.0.7",
"nodemailer": "^6.9.3",
"react-email": "^1.9.4",

View File

@@ -1,145 +0,0 @@
import { type SentMessageInfo, type Transport } from 'nodemailer';
import type Mail from 'nodemailer/lib/mailer';
import type MailMessage from 'nodemailer/lib/mailer/mail-message';
import { Resend } from 'resend';
const VERSION = '1.0.0';
type ResendTransportOptions = {
apiKey: string;
};
type ResendResponseError = {
statusCode: number;
name: string;
message: string;
};
const isResendResponseError = (error: unknown): error is ResendResponseError => {
// We could use Zod here, but it's not worth the extra bundle size
return (
typeof error === 'object' &&
error !== null &&
'statusCode' in error &&
typeof error.statusCode === 'number' &&
'name' in error &&
typeof error.name === 'string' &&
'message' in error &&
typeof error.message === 'string'
);
};
/**
* Transport for sending email via the Resend SDK.
*/
export class ResendTransport implements Transport<SentMessageInfo> {
public name = 'ResendMailTransport';
public version = VERSION;
private _client: Resend;
private _options: ResendTransportOptions;
public static makeTransport(options: Partial<ResendTransportOptions>) {
return new ResendTransport(options);
}
constructor(options: Partial<ResendTransportOptions>) {
const { apiKey = '' } = options;
this._options = {
apiKey,
};
this._client = new Resend(apiKey);
}
public send(mail: MailMessage, callback: (_err: Error | null, _info: SentMessageInfo) => void) {
if (!mail.data.to || !mail.data.from) {
return callback(new Error('Missing required fields "to" or "from"'), null);
}
this._client
.sendEmail({
subject: mail.data.subject ?? '',
from: this.toResendFromAddress(mail.data.from),
to: this.toResendAddresses(mail.data.to),
cc: this.toResendAddresses(mail.data.cc),
bcc: this.toResendAddresses(mail.data.bcc),
html: mail.data.html?.toString() || '',
text: mail.data.text?.toString() || '',
attachments: this.toResendAttachments(mail.data.attachments),
})
.then((response) => {
if (isResendResponseError(response)) {
throw new Error(`[${response.statusCode}]: ${response.name} ${response.message}`);
}
callback(null, response);
})
.catch((error) => {
callback(error, null);
});
}
private toResendAddresses(addresses: Mail.Options['to']) {
if (!addresses) {
return [];
}
if (typeof addresses === 'string') {
return [addresses];
}
if (Array.isArray(addresses)) {
return addresses.map((address) => {
if (typeof address === 'string') {
return address;
}
return address.address;
});
}
return [addresses.address];
}
private toResendFromAddress(address: Mail.Options['from']) {
if (!address) {
return '';
}
if (typeof address === 'string') {
return address;
}
return `${address.name} <${address.address}>`;
}
private toResendAttachments(attachments: Mail.Options['attachments']) {
if (!attachments) {
return [];
}
return attachments.map((attachment) => {
if (!attachment.filename || !attachment.content) {
throw new Error('Attachment is missing filename or content');
}
if (typeof attachment.content === 'string') {
return {
filename: attachment.filename,
content: Buffer.from(attachment.content),
};
}
if (attachment.content instanceof Buffer) {
return {
filename: attachment.filename,
content: attachment.content,
};
}
throw new Error('Attachment content must be a string or a buffer');
});
}
}

View File

@@ -118,7 +118,7 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign
style={{ perspective: 800 }}
>
<motion.div
className="bg-background w-full [--sheen-color:180_180_180] dark:[--sheen-color:200_200_200]"
className="bg-background w-full rounded-lg [--sheen-color:180_180_180] dark:[--sheen-color:200_200_200]"
ref={cardRef}
style={{
perspective: '800',