[제품 개발 이야기 #1] 계정 생성
연휴 동안의 긴 고민 끝에 정리된 방향성과 해결책을 기반으로 드디어 상세한 제품 기획서가 작성되었습니다.
기획서 맛보기 😅
오전에 약 1시간 30분에 걸친 기획안 공유 회의를 하고 오후 부터 개발을 시작하게 되었는데 이제부터 각잡고 굉장히 빠른 스케쥴로 프로토타입 개발 - 배포 - 출시 과정을 돌릴거예요. (꽉 잡으세요!)
오늘은 굉장히 간단한 한가지 기능을 개발했습니다.
이메일을 기반으로 Super Account 생성하기
별 거 아니죠?
사용자가 이메일을 입력하면 superplate에서 커뮤니티를 생성하고 관리할 수 있는 계정을 만들 수 있는 기능이에요.
구글 로그인을 사용할수도 있었지만 향후 알림이나 사용자들의 자연스러운 온보딩 경험을 위해 이메일로 HTML을 전송하는 방식으로 결정했습니다.
@다운 님의 도움을 얻어 두가지 제품을 통해 이메일을 발송할 수 있는 모듈을 개발했어요.
- https://sendgrid.com
- 하루 100개 발송 무료, 이후 유료. 월 1만건을 보낸다면 $19.95
- AWS SES
- 1000건당 $0.1. 만약 월 1만건을 보낸다면 $1
원래는 두 제품중에 하나를 사용하려고 했는데 sendgrid의 어마무시한 가격을 보고 고민을 해보기로 했습니다.
그래서 매일 100건까지는 sendgrid로 발송하고 이후에는 SES로 이메일을 발송하는 형태로 진행했습니다.
오늘 sendgrid로 몇건 발송했는지를 저장하는 기능은 db나 redis류의 캐시를 쓸 것 까진 아니라서 preference 모듈을 활용했어요. sendgrid로 메일을 발송 후 count를 업데이트하고 날짜가 지나면 초기화하는 로직이 담긴 코드입니다.
import path from 'path';
import chalk from 'chalk';
import dayjs from 'dayjs';
import Preference from 'preferences';
import sendgrid from '@sendgrid/mail';
import nodemailer from 'nodemailer';
import AWS from 'aws-sdk';
import dotenv from 'dotenv';
dotenv.config();
AWS.config.region = 'ap-northeast-2';
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
const preferenceOptions = {
encrypt: false,
file: path.resolve('.preference'),
format: 'json'
}
class SendgridInstance
{
constructor()
{
this.preference = new Preference('xyz.superplate.sendgrid', { date: dayjs().format('YYYY-MM-DD'), counter: 0 }, preferenceOptions);
this.counter = this.preference.counter;
this.label = 'Sendgrid';
}
rotate()
{
const today = dayjs().format('YYYY-MM-DD');
if(this.preference.date !== today)
{
this.counter = this.preference.counter = 0;
this.preference.date = today;
}
}
async send(from, to, subject, text, html)
{
const result = await sendgrid.send({ to: to, from: from, subject: subject, text: text, html: html });
this.rotate();
this.counter = ++this.preference.counter;
return result;
}
}
class SESInstance
{
constructor()
{
this.label = 'AWS-SES';
this.counter = 0;
this.instance = nodemailer.createTransport({
SES: new AWS.SES({
apiVersion: '2010-12-01'
})
});
}
async send(from, to, subject, text, html)
{
const result = await this.instance.sendMail({ from: from, to: to, subject: subject, html: html });
this.counter++;
return result;
}
}
class SuperplateMailer
{
constructor()
{
this.sendgrid = new SendgridInstance();
this.ses = new SESInstance();
}
async send(from, to, subject, text, html)
{
if(this.ses && this.sendgrid)
{
const current = (this.sendgrid.counter < 100 ? this.sendgrid : this.ses);
try
{
const result = await current.send.apply(current, arguments);
console.log(chalk.green('\n[SuperplateMailer][' + current.label + '] Total ' + current.counter + ' sent.'));
return result;
}
catch (err)
{
console.error('\n[SuperplateMailer][ERROR]', err);
if(current !== this.ses)
{
return await this.ses.send(from, to, subject, text, html);
}
}
}
else
{
console.log(chalk.red('\n[SuperplateMailer] current instance not defined.'));
}
}
}
export default new SuperplateMailer();
당분간 이 모듈을 통해서 테스트를 진행하면서 안정적으로 메일이 발송되는지 체크를 해본 뒤에 최종족으로 어떤 구성으로 갈지 결정할 수 있을 것 같네요.
아니면 쭉 이렇게 갈수도...🤔
목표는 내일 계정 생성 후 Waitlist를 신청받는 기능까지 배포 후 메이커 로그를 작성하는 것이고, 아직 신청하지 않으신 분들은 계정 생성 후 신청, 이미 신청하신 분들이나 저에게 명함을 주신 분들은 이메일로 초대를 드릴 예정입니다!
고객 커뮤니티 SaaS, 고객들과 소통할 수 있는 커뮤니티를 기반으로 제품 주도 성장을 돕습니다.
댓글
로그인 후 댓글을 남길 수 있습니다.
여전히 코드는 아름답습니다. 한 때는 세상에서 제일 부러운 사람이 코딩하는 사람이었다는... 인물도 좋으신데 저런 능력까지.
꽉 잡았습니다!!
저도 꽉 잡았읍니다.
엄청나게 꼼꼼하십니다. 리스펙!!! 입니다.