Skip to main content

max / makenotwork

7.0 KB · 272 lines History Blame Raw
1 //! Authentication and account action email templates.
2
3 use crate::db::ListKind;
4 use crate::email::{Audience, Delivery, EmailClass, EmailClient, OperationalKind};
5 use crate::error::Result;
6
7 impl EmailClient {
8 /// Send a password reset email
9 pub async fn send_password_reset(
10 &self,
11 to_email: &str,
12 to_name: Option<&str>,
13 reset_url: &str,
14 ) -> Result<()> {
15 let subject = "Reset your password";
16 let body = format!(
17 r"Hi{name},
18
19 You requested to reset your password. Click the link below to set a new password:
20
21 {url}
22
23 This link expires in 15 minutes. If you didn't request this, you can ignore this email.
24
25 - Makenotwork",
26 name = crate::email::greeting(to_name),
27 url = reset_url
28 );
29
30 self.dispatch(
31 EmailClass::Operational(OperationalKind::PasswordReset),
32 Audience::Address(to_email),
33 subject,
34 &body,
35 Delivery::default(),
36 )
37 .await
38 }
39
40 /// Send an email verification email
41 pub async fn send_verification(
42 &self,
43 to_email: &str,
44 to_name: Option<&str>,
45 verify_url: &str,
46 ) -> Result<()> {
47 let subject = "Verify your email";
48 let body = format!(
49 r"Hi{name},
50
51 Please verify your email address by clicking the link below:
52
53 {url}
54
55 This link expires in 24 hours.
56
57 - Makenotwork",
58 name = crate::email::greeting(to_name),
59 url = verify_url
60 );
61
62 self.dispatch(
63 EmailClass::Operational(OperationalKind::EmailVerification),
64 Audience::Address(to_email),
65 subject,
66 &body,
67 Delivery::default(),
68 )
69 .await
70 }
71
72 /// Sent when someone tries to sign up with an email that already has an
73 /// account. The signup page deliberately does not reveal that the address is
74 /// registered (enumeration defense); this email is the channel that reaches
75 /// the real owner so they can recover, since only they receive it.
76 pub async fn send_account_exists(
77 &self,
78 to_email: &str,
79 login_url: &str,
80 reset_url: &str,
81 ) -> Result<()> {
82 let subject = "You already have a Makenotwork account";
83 let body = format!(
84 r"Hi,
85
86 Someone (probably you) just tried to sign up for Makenotwork with this email
87 address, but you already have an account.
88
89 To sign in:
90
91 {login_url}
92
93 If you've forgotten your password, you can reset it here:
94
95 {reset_url}
96
97 If this wasn't you, nothing was created or changed and you can safely ignore
98 this email.
99
100 - Makenotwork",
101 );
102
103 self.dispatch(
104 EmailClass::Operational(OperationalKind::AccountExists),
105 Audience::Address(to_email),
106 subject,
107 &body,
108 Delivery::default(),
109 )
110 .await
111 }
112
113 /// Send an account lockout notification
114 pub async fn send_lockout_notification(
115 &self,
116 to_email: &str,
117 to_name: Option<&str>,
118 login_link_url: Option<&str>,
119 ) -> Result<()> {
120 let subject = "Security alert: Account locked";
121 let body = format!(
122 r"Hi{name},
123
124 Your account has been temporarily locked due to multiple failed login attempts.
125
126 {login_link}
127
128 If this wasn't you, please contact support immediately.
129
130 - Makenotwork",
131 name = crate::email::greeting(to_name),
132 login_link = login_link_url.map_or_else(
133 || "Your account will unlock automatically in 15 minutes.".to_string(),
134 |url| format!(
135 "Use this link to log in securely:\n\n{url}\n\nThis link expires in 15 minutes."
136 )
137 )
138 );
139
140 self.dispatch(
141 EmailClass::Operational(OperationalKind::AccountLockout),
142 Audience::Address(to_email),
143 subject,
144 &body,
145 Delivery::default(),
146 )
147 .await
148 }
149
150 /// Send a one-time login link
151 pub async fn send_login_link(
152 &self,
153 to_email: &str,
154 to_name: Option<&str>,
155 login_url: &str,
156 ) -> Result<()> {
157 let subject = "Your login link";
158 let body = format!(
159 r"Hi{name},
160
161 Click the link below to log in to your account:
162
163 {url}
164
165 This link expires in 15 minutes and can only be used once.
166
167 If you didn't request this, you can ignore this email.
168
169 - Makenotwork",
170 name = crate::email::greeting(to_name),
171 url = login_url
172 );
173
174 self.dispatch(
175 EmailClass::Operational(OperationalKind::LoginLink),
176 Audience::Address(to_email),
177 subject,
178 &body,
179 Delivery::default(),
180 )
181 .await
182 }
183
184 /// Send account deletion confirmation email
185 pub async fn send_deletion_confirmation(
186 &self,
187 to_email: &str,
188 to_name: Option<&str>,
189 delete_url: &str,
190 ) -> Result<()> {
191 let subject = "Confirm account deletion";
192 let body = format!(
193 r"Hi{name},
194
195 You requested to delete your Makenotwork account.
196
197 IMPORTANT: This action is permanent and cannot be undone.
198
199 Clicking the link below will immediately and permanently delete:
200 - All your projects and items
201 - All uploaded content (audio, images)
202 - Your profile and account settings
203 - Your custom links
204
205 Purchases made by your fans will remain accessible to them.
206
207 To confirm deletion, click this link:
208
209 {url}
210
211 This link expires in 1 hour.
212
213 If you did not request this, do NOT click the link. Your account is safe.
214
215 - Makenotwork",
216 name = crate::email::greeting(to_name),
217 url = delete_url
218 );
219
220 self.dispatch(
221 EmailClass::Operational(OperationalKind::DeletionConfirmation),
222 Audience::Address(to_email),
223 subject,
224 &body,
225 Delivery::default(),
226 )
227 .await
228 }
229
230 /// Send a new-device login notification
231 pub async fn send_new_login_notification(
232 &self,
233 to_user_id: crate::db::UserId,
234 to_email: &str,
235 to_name: Option<&str>,
236 device: Option<&str>,
237 ip: Option<&str>,
238 unsub_url: Option<&str>,
239 ) -> Result<()> {
240 let subject = "New sign-in to your account";
241 let device_line = device.unwrap_or("Unknown device");
242 let ip_line = ip.unwrap_or("Unknown");
243 let body = format!(
244 r"Hi{name},
245
246 Your account was just signed in to from a new device.
247
248 Device: {device}
249 IP address: {ip}
250
251 If this was you, no action is needed. If you don't recognize this sign-in, go to your dashboard and revoke the session under Settings > Sessions.
252
253 - Makenotwork",
254 name = crate::email::greeting(to_name),
255 device = device_line,
256 ip = ip_line,
257 );
258
259 self.dispatch(
260 EmailClass::Optional(ListKind::Login),
261 Audience::User(to_user_id, to_email),
262 subject,
263 &body,
264 Delivery {
265 unsub_url,
266 ..Default::default()
267 },
268 )
269 .await
270 }
271 }
272