Skip to content

Commit

Permalink
add email status change notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
alaister committed Oct 26, 2024
1 parent 185d4c9 commit afba918
Show file tree
Hide file tree
Showing 7 changed files with 1,936 additions and 27 deletions.
1,694 changes: 1,669 additions & 25 deletions deno.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions supabase/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,9 @@ vector_port = 54328
gcp_project_id = ""
gcp_project_number = ""
gcp_jwt_path = "supabase/gcloud.json"

[functions.notify-domain-status-change]
enabled = true

[functions.update-whois]
enabled = true
151 changes: 151 additions & 0 deletions supabase/functions/_email/StatusChangeNotificationEmail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import {
Body,
Column,
Container,
Head,
Heading,
Html,
Link,
Preview,
Row,
Section,
Text,
} from '@react-email/components'
import { format } from 'date-fns'
import * as React from 'react'

interface StatusChangeNotificationEmailProps {
domain_name: string
previous_status: string
new_status: string
updated_at: Date
}

export const StatusChangeNotificationEmail = ({
domain_name,
previous_status,
new_status,
updated_at,
}: StatusChangeNotificationEmailProps) => {
const previewText = `Status update for ${domain_name}: ${previous_status}${new_status}`

return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Body style={main}>
<Container style={container}>
<Heading style={heading}>
Domain Status Update for {domain_name}
</Heading>

<Section style={section}>
<Text style={statusText}>Status changed from</Text>

<Row style={statusRow}>
<Column align="left">
<Text style={statusPill}>{previous_status}</Text>
</Column>
<Column align="center">
<Text style={statusTextTo}>to</Text>
</Column>
<Column align="right">
<Text style={statusPill}>{new_status}</Text>
</Column>
</Row>

<Text style={timestamp}>
Updated on {format(updated_at, "MMMM do, yyyy 'at' h:mm:ss a")}
</Text>
</Section>

<Text style={footer}>
This email was sent by{' '}
<Link href="https://side.domains" style={link}>
side.domains
</Link>
. If you don't want to receive status notifications for this domain,{' '}
<Link href="https://app.side.domains" style={link}>
visit the dashboard
</Link>
.
</Text>
</Container>
</Body>
</Html>
)
}

const main = {
backgroundColor: '#ffffff',
fontFamily: '-apple-system, "Segoe UI", sans-serif',
}

const container = {
maxWidth: '600px',
margin: '0 auto',
padding: '20px',
}

const heading = {
color: '#111827',
fontSize: '24px',
fontWeight: '400',
textAlign: 'left',
margin: '16px 0',
}

const section = {
backgroundColor: '#f3f4f6',
borderRadius: '12px',
padding: '24px',
margin: '24px 0',
}

const statusRow = {
display: 'flex',
alignItems: 'center',
gap: '12px',
}

const statusText = {
color: '#4b5563',
fontSize: '16px',
margin: '12px 0',
}

const statusTextTo = {
...statusText,
margin: '12px',
}

const statusPill = {
backgroundColor: '#6b7280',
color: '#ffffff',
padding: '8px 16px',
borderRadius: '16px',
fontSize: '14px',
fontWeight: '500',
display: 'inline-block',
}

const timestamp = {
color: '#6b7280',
fontSize: '14px',
margin: '20px 0 0 0',
}

const footer = {
color: '#6b7280',
fontSize: '14px',
lineHeight: '24px',
margin: '32px 0',
textAlign: 'center',
}

const link = {
color: '#2563eb',
textDecoration: 'underline',
}

export default StatusChangeNotificationEmail
7 changes: 6 additions & 1 deletion supabase/functions/import_map.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"imports": {
"@supabase/supabase-js": "jsr:@supabase/supabase-js@^2.45.4"
"@react-email/components": "npm:@react-email/components@^0.0.25",
"@supabase/supabase-js": "jsr:@supabase/supabase-js@^2.45.4",
"date-fns": "npm:date-fns@^4.1.0",
"react": "npm:react@^18.3.1",
"react-dom": "npm:react-dom@^18.3.1",
"resend": "npm:resend@^4.0.0"
}
}
60 changes: 60 additions & 0 deletions supabase/functions/notify-domain-status-change/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'

import { createClient } from '@supabase/supabase-js'
import { Resend } from 'resend'
import StatusChangeNotificationEmail from '../_email/StatusChangeNotificationEmail.tsx'

const resend = new Resend(Deno.env.get('RESEND_API_KEY')!)

Deno.serve(async (req) => {
if (req.method !== 'POST') {
return new Response(null, { status: 405 })
}

const serviceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')
const token = req.headers.get('Authorization')?.replace('Bearer ', '')

if (!serviceRoleKey || !token || token !== serviceRoleKey) {
return new Response(null, { status: 401 })
}

const supabaseAdmin = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)

const { new_status, previous_status, domain_name_id } = await req.json()

const { data } = await supabaseAdmin
.from('domain_names')
.select()
.eq('id', domain_name_id)
.maybeSingle()

if (!data) {
return new Response(null, { status: 404 })
}

const { domain_name, updated_at } = data

const { error } = await resend.emails.send({
from: 'side.domains Notifications <[email protected]>',
to: '[email protected]',
subject: `Status update for ${data.domain_name}: ${previous_status}${new_status}`,
react: StatusChangeNotificationEmail({
domain_name,
previous_status,
new_status,
updated_at,
}),
})

if (error) {
console.error('error sending email:', error)
return new Response(null, { status: 500 })
}

return new Response(JSON.stringify({ sent: true }), {
headers: { 'Content-Type': 'application/json' },
})
})
4 changes: 3 additions & 1 deletion supabase/migrations/20240917123202_update_whois_function.sql
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ begin
'Content-Type','application/json',
'Authorization', concat('Bearer ', v_supabase_service_role_key)
),
body := concat('{"id": "', v_domain_name_id::text, '"}')::jsonb
body := jsonb_build_object(
'id', v_domain_name_id::text
)
);
end;
$$ language plpgsql security invoker volatile;
Expand Down
41 changes: 41 additions & 0 deletions supabase/migrations/20241026160057_notify_domain_status_change.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
create
or replace function public.notify_domain_status_change () returns trigger as $$
declare
v_supabase_url text;
v_supabase_service_role_key text;
begin
select decrypted_secret into v_supabase_url
from vault.decrypted_secrets
where name = 'supabase-url';

select decrypted_secret into v_supabase_service_role_key
from vault.decrypted_secrets
where name = 'supabase-service-role-key';

perform net.http_post(
url := concat(v_supabase_url, '/functions/v1/notify-domain-status-change'),
headers := jsonb_build_object(
'Content-Type','application/json',
'Authorization', concat('Bearer ', v_supabase_service_role_key)
),
body := jsonb_build_object(
'domain_name_id', new.id,
'previous_status', old.status,
'new_status', new.status
)
);

return new;
end;
$$ language plpgsql volatile;

create trigger notify_domain_status_change
after
update on public.domain_names for each row when (
new.status_change_notifications_enabled = true
and old.status <> 'unknown'::domain_name_status
and old.status is distinct
from
new.status
)
execute procedure public.notify_domain_status_change ();

0 comments on commit afba918

Please sign in to comment.