forked from clerk/javascript
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathemailService.ts
More file actions
72 lines (65 loc) · 2.44 KB
/
Copy pathemailService.ts
File metadata and controls
72 lines (65 loc) · 2.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import { runWithExponentialBackOff } from '@clerk/shared';
type Message = {
_id: string;
subject: string;
};
export const createEmailService = () => {
const cleanEmail = (email: string) => {
return email.replace(/\+.*@/, '@');
};
const fetcher = async (url: string | URL, init?: RequestInit) => {
const headers = new Headers(init?.headers || {});
headers.set('Mailsac-Key', process.env.MAILSAC_API_KEY as string);
return fetch(url, { ...init, headers });
};
const filterMessagesByAddress = async (email: string, sub?: string) => {
const url = new URL('https://mailsac.com/api/inbox-filter');
url.searchParams.set('andTo', email);
if (sub) {
url.searchParams.set('andSubjectIncludes', sub);
}
// Retry in case the email delivery is delayed
await new Promise(res => setTimeout(res, 1500));
return runWithExponentialBackOff(
async () => {
const res = await fetcher(url);
const json = (await res.json()) as unknown as { messages: Message[] };
const message = json.messages[0];
if (!message) {
throw new Error('message not found');
}
return message;
},
{
firstDelay: 750,
timeMultiple: 2,
shouldRetry: (_, iterationsCount) => iterationsCount < 5,
},
);
};
const getMessagePlaintextForAddress = async (email: string, id: string) => {
const url = new URL(`https://mailsac.com/api/text/${cleanEmail(email)}/${id}`);
const res = await fetcher(url);
return res.text();
};
const deleteMessage = async (email: string, id: string) => {
// best-effort file-and-forget delete
const url = new URL(`https://mailsac.com/api/addresses/${cleanEmail(email)}/messages/${id}`);
return fetcher(url, { method: 'DELETE' });
};
return {
getCodeForEmailAddress: async (email: string) => {
const message = await filterMessagesByAddress(email, 'verification code');
const code = (message.subject.match(/\d{6}/)?.[0] || '').trim();
void deleteMessage(email, message._id);
return code;
},
getVerificationLinkForEmailAddress: async (email: string) => {
const message = await filterMessagesByAddress(email, 'link');
const body = await getMessagePlaintextForAddress(email, message._id);
const link = (body.match(/https:\/\/.*\/verify\?.*/) || [''])[0].trim().replace(/&/g, '&');
void deleteMessage(email, message._id);
return link;
},
};
};