Merge branch 'feat/refresh' into docs/minor-readme-updatess
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: The Early Adopters Plan
|
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'
|
authorName: 'Timur Ercan'
|
||||||
authorImage: '/blog/blog-author-timur.jpeg'
|
authorImage: '/blog/blog-author-timur.jpeg'
|
||||||
authorRole: 'Co-Founder'
|
authorRole: 'Co-Founder'
|
||||||
date: 2023-09-20
|
date: 2023-09-28
|
||||||
tags:
|
tags:
|
||||||
- Paid Plan
|
- Paid Plan
|
||||||
- Metrics
|
- Metrics
|
||||||
@@ -24,6 +24,8 @@ tags:
|
|||||||
</figcaption>
|
</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
|
> TLDR; we have 100 early adopter accounts available at a great price. [Secure yours now](https://documenso.com/pricing).
|
||||||
|
|
||||||
## Community-Driven Development
|
## 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.
|
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
|
## 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).
|
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
12
package-lock.json
generated
@@ -1868,6 +1868,17 @@
|
|||||||
"resolved": "apps/marketing",
|
"resolved": "apps/marketing",
|
||||||
"link": true
|
"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": {
|
"node_modules/@documenso/prettier-config": {
|
||||||
"resolved": "packages/prettier-config",
|
"resolved": "packages/prettier-config",
|
||||||
"link": true
|
"link": true
|
||||||
@@ -19794,6 +19805,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@documenso/nodemailer-resend": "1.0.0",
|
||||||
"@react-email/components": "^0.0.7",
|
"@react-email/components": "^0.0.7",
|
||||||
"nodemailer": "^6.9.3",
|
"nodemailer": "^6.9.3",
|
||||||
"react-email": "^1.9.4",
|
"react-email": "^1.9.4",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { createTransport } from 'nodemailer';
|
import { createTransport } from 'nodemailer';
|
||||||
|
|
||||||
|
import { ResendTransport } from '@documenso/nodemailer-resend';
|
||||||
|
|
||||||
import { MailChannelsTransport } from './transports/mailchannels';
|
import { MailChannelsTransport } from './transports/mailchannels';
|
||||||
import { ResendTransport } from './transports/resend';
|
|
||||||
|
|
||||||
const getTransport = () => {
|
const getTransport = () => {
|
||||||
const transport = process.env.NEXT_PRIVATE_SMTP_TRANSPORT ?? 'smtp-auth';
|
const transport = process.env.NEXT_PRIVATE_SMTP_TRANSPORT ?? 'smtp-auth';
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"worker:test": "tsup worker/index.ts --format esm"
|
"worker:test": "tsup worker/index.ts --format esm"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@documenso/nodemailer-resend": "1.0.0",
|
||||||
"@react-email/components": "^0.0.7",
|
"@react-email/components": "^0.0.7",
|
||||||
"nodemailer": "^6.9.3",
|
"nodemailer": "^6.9.3",
|
||||||
"react-email": "^1.9.4",
|
"react-email": "^1.9.4",
|
||||||
|
|||||||
@@ -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');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -118,7 +118,7 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign
|
|||||||
style={{ perspective: 800 }}
|
style={{ perspective: 800 }}
|
||||||
>
|
>
|
||||||
<motion.div
|
<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}
|
ref={cardRef}
|
||||||
style={{
|
style={{
|
||||||
perspective: '800',
|
perspective: '800',
|
||||||
|
|||||||
Reference in New Issue
Block a user