Skip to content

Instantly share code, notes, and snippets.

@arash16
Created January 18, 2023 10:52
Show Gist options
  • Save arash16/947d44c8a8f5d7f9ea3532a93181b28f to your computer and use it in GitHub Desktop.
Save arash16/947d44c8a8f5d7f9ea3532a93181b28f to your computer and use it in GitHub Desktop.
SENDGRID_KEY=sendgrid-api-key
SECRET=secret-to-sign-subscription-links
import createSendgridClient from '../sendgrid';
import { hash } from '../utils';
interface Env {
SECRET: string;
SENDGRID_KEY: string;
}
export const onRequestPost: PagesFunction<Env> = async context => {
const { email } = await context.request.json<{ email?: string }>();
// TODO: validate captcha
const secret = context.env.SECRET;
const hashed = await hash(email, secret);
const verifyLink = `https://website/api/verify?email=${encodeURIComponent(
email,
)}&hash=${hashed}`;
const sg = createSendgridClient(context.env.SENDGRID_KEY);
await sg.sendMail({
to: email,
from: 'me@arash16.com', // Use the email address or domain you verified above
subject: 'Confirm email subscription',
text: `Please confirm your subscription by opening following link:
${verifyLink}`,
html: `<p>Please confirm your subscription by opening following link:</p>
<p><a href="${verifyLink}">Verify</a></p>`,
});
return Response.json({ email }, { status: 200 });
};
import createSendgridClient from '../sendgrid';
import { hash } from '../utils';
interface Env {
SECRET: string;
SENDGRID_KEY: string;
}
export const onRequestPost: PagesFunction<Env> = async context => {
const url = new URL(context.request.url);
const email = url.searchParams.get('email');
const hashed = url.searchParams.get('email');
const secret = context.env.SECRET;
if (hashed !== (await hash(email, secret))) {
return Response.redirect('/subscription?error=invalid-hash', 302);
}
const sg = createSendgridClient(context.env.SENDGRID_KEY);
await sg.addContact(['881a7cf5-c79e-495a-804e-e1b42e91cc3b'], email);
return Response.redirect('/subscription?email=' + email, 302);
};
interface EmailOptions {
to: string;
from: string;
subject: string;
text: string;
html: string;
}
const createSendgridClient = (key: string) => {
const call = (url: string, options: RequestInit<RequestInitCfProperties>) =>
fetch('https://api.sendgrid.com' + url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${key}`,
'Content-Type': 'application/json',
},
});
return {
sendMail(opts: EmailOptions) {
return call('/v3/mail/send', {
method: 'POST',
body: JSON.stringify({
from: { name: 'Arash16', email: opts.from },
personalizations: [{ to: [{ email: opts.to }] }],
subject: opts.subject,
content: [
{
type: 'text/html',
value: opts.html,
},
],
// 'template_id': '',
}),
});
},
addContact(lists: string[], email: string, data?: Record<string, string>) {
return call('/v3/marketing/contacts', {
method: 'PUT',
body: JSON.stringify({
list_ids: lists,
contacts: [
{
email,
custom_fields: data,
},
],
}),
}).then(x => x.json());
},
removeContact(contactID: string) {
return call('/v3/marketing/contacts?ids=' + contactID, {
method: 'DELETE',
}).then(x => x.json());
},
};
};
export default createSendgridClient;
{
"include": ["./**/*"],
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"allowSyntheticDefaultImports": true,
"moduleResolution": "Node",
"lib": ["esnext"],
"types": ["@cloudflare/workers-types"]
}
}
export async function hash(...args: string[]) {
const str = args.join(':');
const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str));
return [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, '0')).join('');
}
{
"version": 1,
"include": ["/api/*"],
"exclude": []
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment