Skip to content

Commit

Permalink
Migrate from Postmark to Brevo
Browse files Browse the repository at this point in the history
European, nice plans, and very easy-to-use API.
  • Loading branch information
BrunoBernardino committed Sep 13, 2023
1 parent 4222b61 commit b66f00e
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 170 deletions.
2 changes: 1 addition & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ POSTGRESQL_DBNAME="budgetzen"
POSTGRESQL_PORT=5432
POSTGRESQL_CAFILE=""

POSTMARK_SERVER_API_TOKEN="fake"
BREVO_API_KEY="fake"

STRIPE_API_KEY="fake"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ It's not compatible with Budget Zen v2 ([end-to-end encrypted via Userbase](http

Or check the [Development section below](#development).

> **NOTE:** You don't need to have emails (Postmark) and subscriptions (Stripe) setup to have the app work. Those are only used for allowing others to automatically manage their account. You can simply make any `user.status = 'active'` and `user.subscription.expires_at = new Date('2100-01-01')` to "never" expire, in the database, directly.
> **NOTE:** You don't need to have emails (Brevo) and subscriptions (Stripe) setup to have the app work. Those are only used for allowing others to automatically manage their account. You can simply make any `user.status = 'active'` and `user.subscription.expires_at = new Date('2100-01-01')` to "never" expire, in the database, directly.
## Framework-less

Expand Down
2 changes: 1 addition & 1 deletion crons/check-subscriptions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Database, { sql } from '/lib/interfaces/database.ts';
import { getSubscriptions as getStripeSubscriptions } from '/lib/providers/stripe.ts';
import { sendSubscriptionExpiredEmail } from '/lib/providers/postmark.ts';
import { sendSubscriptionExpiredEmail } from '/lib/providers/brevo.ts';
import { updateUser } from '/lib/data-utils.ts';
import { User } from '/lib/types.ts';

Expand Down
153 changes: 153 additions & 0 deletions lib/providers/brevo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import 'std/dotenv/load.ts';

import { helpEmail } from '/lib/utils.ts';

const BREVO_API_KEY = Deno.env.get('BREVO_API_KEY') || '';

enum BrevoTemplateId {
BUDGETZEN_VERIFY_LOGIN = 8,
BUDGETZEN_VERIFY_UPDATE = 9,
BUDGETZEN_VERIFY_DELETE = 10,
BUDGETZEN_UPDATE_BILLING_EMAIL = 11,
BUDGETZEN_SUBSCRIPTION_EXPIRED = 12,
}

interface BrevoResponse {
messageId?: string;
code?: string;
message?: string;
}

function getApiRequestHeaders() {
return {
'Api-Key': BREVO_API_KEY,
'Accept': 'application/json; charset=utf-8',
'Content-Type': 'application/json; charset=utf-8',
};
}

interface BrevoRequestBody {
templateId?: number;
params: Record<string, any>;
to: { email: string; name?: string }[];
cc?: { email: string; name?: string }[];
bcc?: { email: string; name?: string }[];
htmlContent?: string;
textContent?: string;
subject?: string;
replyTo: { email: string; name?: string };
tags?: string[];
attachment?: { name: string; content: string; url: string }[];
}

async function sendEmailWithTemplate(
to: string,
templateId: BrevoTemplateId,
data: BrevoRequestBody['params'],
attachments: BrevoRequestBody['attachment'] = [],
cc?: string,
) {
const email: BrevoRequestBody = {
templateId,
params: data,
to: [{ email: to }],
replyTo: { email: helpEmail },
};

if (attachments?.length) {
email.attachment = attachments;
}

if (cc) {
email.cc = [{ email: cc }];
}

const brevoResponse = await fetch('https://api.brevo.com/v3/smtp/email', {
method: 'POST',
headers: getApiRequestHeaders(),
body: JSON.stringify(email),
});
const brevoResult = (await brevoResponse.json()) as BrevoResponse;

if (brevoResult.code || brevoResult.message) {
console.log(JSON.stringify({ brevoResult }, null, 2));
throw new Error(`Failed to send email "${templateId}"`);
}
}

export async function sendVerifyLoginEmail(
email: string,
verificationCode: string,
) {
const data = {
verificationCode,
};

await sendEmailWithTemplate(email, BrevoTemplateId.BUDGETZEN_VERIFY_LOGIN, data);
}

export async function sendVerifyDeleteDataEmail(
email: string,
verificationCode: string,
) {
const data = {
verificationCode,
deletionSubject: 'all your data',
};

await sendEmailWithTemplate(email, BrevoTemplateId.BUDGETZEN_VERIFY_DELETE, data);
}

export async function sendVerifyDeleteAccountEmail(
email: string,
verificationCode: string,
) {
const data = {
verificationCode,
deletionSubject: 'your account',
};

await sendEmailWithTemplate(email, BrevoTemplateId.BUDGETZEN_VERIFY_DELETE, data);
}

export async function sendVerifyUpdateEmailEmail(
email: string,
verificationCode: string,
) {
const data = {
verificationCode,
updateSubject: 'your email',
};

await sendEmailWithTemplate(email, BrevoTemplateId.BUDGETZEN_VERIFY_UPDATE, data);
}

export async function sendVerifyUpdatePasswordEmail(
email: string,
verificationCode: string,
) {
const data = {
verificationCode,
updateSubject: 'your password',
};

await sendEmailWithTemplate(email, BrevoTemplateId.BUDGETZEN_VERIFY_UPDATE, data);
}

export async function sendUpdateEmailInProviderEmail(
oldEmail: string,
newEmail: string,
) {
const data = {
oldEmail,
newEmail,
};

await sendEmailWithTemplate(helpEmail, BrevoTemplateId.BUDGETZEN_UPDATE_BILLING_EMAIL, data);
}

export async function sendSubscriptionExpiredEmail(
email: string,
) {
await sendEmailWithTemplate(email, BrevoTemplateId.BUDGETZEN_SUBSCRIPTION_EXPIRED, {});
}
164 changes: 0 additions & 164 deletions lib/providers/postmark.ts

This file was deleted.

2 changes: 1 addition & 1 deletion pages/api/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
validateUserAndSession,
validateVerificationCode,
} from '/lib/data-utils.ts';
import { sendVerifyDeleteDataEmail } from '/lib/providers/postmark.ts';
import { sendVerifyDeleteDataEmail } from '/lib/providers/brevo.ts';
import { Budget, Expense } from '/lib/types.ts';

async function importDataAction(request: Request) {
Expand Down
2 changes: 1 addition & 1 deletion pages/api/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
validateUserAndSession,
validateVerificationCode,
} from '/lib/data-utils.ts';
import { sendVerifyLoginEmail } from '/lib/providers/postmark.ts';
import { sendVerifyLoginEmail } from '/lib/providers/brevo.ts';

async function validateSession(request: Request) {
const { email }: { email: string } = await request.json();
Expand Down
2 changes: 1 addition & 1 deletion pages/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
sendVerifyDeleteAccountEmail,
sendVerifyUpdateEmailEmail,
sendVerifyUpdatePasswordEmail,
} from '/lib/providers/postmark.ts';
} from '/lib/providers/brevo.ts';
import { SupportedCurrencySymbol, validateEmail } from '/public/ts/utils.ts';
import { PAYPAL_CUSTOMER_URL, STRIPE_CUSTOMER_URL } from '/lib/utils.ts';
import { createCustomerPortalSession } from '/lib/providers/stripe.ts';
Expand Down

0 comments on commit b66f00e

Please sign in to comment.