@@ -3,6 +3,16 @@ import { createTransport } from "nodemailer"
3
3
import type { CommonProviderOptions } from "."
4
4
import type { Options as SMTPConnectionOptions } from "nodemailer/lib/smtp-connection"
5
5
import type { Awaitable } from ".."
6
+ import type { Theme } from "../core/types"
7
+
8
+ export interface SendVerificationRequestParams {
9
+ identifier : string
10
+ url : string
11
+ expires : Date
12
+ provider : EmailConfig
13
+ token : string
14
+ theme : Theme
15
+ }
6
16
7
17
export interface EmailConfig extends CommonProviderOptions {
8
18
type : "email"
@@ -16,13 +26,10 @@ export interface EmailConfig extends CommonProviderOptions {
16
26
* @default 86400
17
27
*/
18
28
maxAge ?: number
19
- sendVerificationRequest : ( params : {
20
- identifier : string
21
- url : string
22
- expires : Date
23
- provider : EmailConfig
24
- token : string
25
- } ) => Awaitable < void >
29
+ /** [Documentation](https://next-auth.js.org/providers/email#customizing-emails) */
30
+ sendVerificationRequest : (
31
+ params : SendVerificationRequestParams
32
+ ) => Awaitable < void >
26
33
/**
27
34
* By default, we are generating a random verification token.
28
35
* You can make it predictable or modify it as you like with this method.
@@ -56,78 +63,81 @@ export default function Email(options: EmailUserConfig): EmailConfig {
56
63
type : "email" ,
57
64
name : "Email" ,
58
65
// Server can be an SMTP connection string or a nodemailer config object
59
- server : {
60
- host : "localhost" ,
61
- port : 25 ,
62
- auth : {
63
- user : "" ,
64
- pass : "" ,
65
- } ,
66
- } ,
66
+ server : { host : "localhost" , port : 25 , auth : { user : "" , pass : "" } } ,
67
67
from : "NextAuth <no-reply@example.com>" ,
68
68
maxAge : 24 * 60 * 60 ,
69
- async sendVerificationRequest ( {
70
- identifier : email ,
71
- url,
72
- provider : { server, from } ,
73
- } ) {
69
+ async sendVerificationRequest ( params ) {
70
+ const { identifier, url, provider, theme } = params
74
71
const { host } = new URL ( url )
75
- const transport = createTransport ( server )
76
- await transport . sendMail ( {
77
- to : email ,
78
- from,
72
+ const transport = createTransport ( provider . server )
73
+ const result = await transport . sendMail ( {
74
+ to : identifier ,
75
+ from : provider . from ,
79
76
subject : `Sign in to ${ host } ` ,
80
77
text : text ( { url, host } ) ,
81
- html : html ( { url, host, email } ) ,
78
+ html : html ( { url, host, theme } ) ,
82
79
} )
80
+ const failed = result . rejected . concat ( result . pending ) . filter ( Boolean )
81
+ if ( failed . length ) {
82
+ throw new Error ( `Email(s) (${ failed . join ( ", " ) } ) could not be sent` )
83
+ }
83
84
} ,
84
85
options,
85
86
}
86
87
}
87
88
88
- // Email HTML body
89
- function html ( { url, host, email } : Record < "url" | "host" | "email" , string > ) {
90
- // Insert invisible space into domains and email address to prevent both the
91
- // email address and the domain from being turned into a hyperlink by email
92
- // clients like Outlook and Apple mail, as this is confusing because it seems
93
- // like they are supposed to click on their email address to sign in.
94
- const escapedEmail = `${ email . replace ( / \. / g, "​." ) } `
95
- const escapedHost = `${ host . replace ( / \. / g, "​." ) } `
89
+ /**
90
+ * Email HTML body
91
+ * Insert invisible space into domains from being turned into a hyperlink by email
92
+ * clients like Outlook and Apple mail, as this is confusing because it seems
93
+ * like they are supposed to click on it to sign in.
94
+ *
95
+ * @note We don't add the email address to avoid needing to escape it, if you do, remember to sanitize it!
96
+ */
97
+ function html ( params : { url : string ; host : string ; theme : Theme } ) {
98
+ const { url, host, theme } = params
99
+
100
+ const escapedHost = host . replace ( / \. / g, "​." )
101
+
102
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
103
+ const brandColor = theme . brandColor || "#346df1"
104
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
105
+ const buttonText = theme . buttonText || "#fff"
96
106
97
- // Some simple styling options
98
- const backgroundColor = "#f9f9f9"
99
- const textColor = "#444444"
100
- const mainBackgroundColor = "#ffffff"
101
- const buttonBackgroundColor = "#346df1"
102
- const buttonBorderColor = "#346df1"
103
- const buttonTextColor = "#ffffff"
107
+ const color = {
108
+ background : "#f9f9f9" ,
109
+ text : "#444" ,
110
+ mainBackground : "#fff" ,
111
+ buttonBackground : brandColor ,
112
+ buttonBorder : brandColor ,
113
+ buttonText,
114
+ }
104
115
105
116
return `
106
- <body style="background: ${ backgroundColor } ;">
107
- <table width="100%" border="0" cellspacing="0" cellpadding="0">
108
- <tr>
109
- <td align="center" style="padding: 10px 0px 20px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${ textColor } ;">
110
- <strong>${ escapedHost } </strong>
111
- </td>
112
- </tr>
113
- </table>
114
- <table width="100%" border="0" cellspacing="20" cellpadding="0" style="background: ${ mainBackgroundColor } ; max-width: 600px; margin: auto; border-radius: 10px;">
117
+ <body style="background: ${ color . background } ;">
118
+ <table width="100%" border="0" cellspacing="20" cellpadding="0"
119
+ style="background: ${ color . mainBackground } ; max-width: 600px; margin: auto; border-radius: 10px;">
115
120
<tr>
116
- <td align="center" style="padding: 10px 0px 0px 0px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${ textColor } ;">
117
- Sign in as <strong>${ escapedEmail } </strong>
121
+ <td align="center"
122
+ style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${ color . text } ;">
123
+ Sign in to <strong>${ escapedHost } </strong>
118
124
</td>
119
125
</tr>
120
126
<tr>
121
127
<td align="center" style="padding: 20px 0;">
122
128
<table border="0" cellspacing="0" cellpadding="0">
123
129
<tr>
124
- <td align="center" style="border-radius: 5px;" bgcolor="${ buttonBackgroundColor } "><a href="${ url } " target="_blank" style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${ buttonTextColor } ; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${ buttonBorderColor } ; display: inline-block; font-weight: bold;">Sign in</a></td>
130
+ <td align="center" style="border-radius: 5px;" bgcolor="${ color . buttonBackground } "><a href="${ url } "
131
+ target="_blank"
132
+ style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${ color . buttonText } ; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${ color . buttonBorder } ; display: inline-block; font-weight: bold;">Sign
133
+ in</a></td>
125
134
</tr>
126
135
</table>
127
136
</td>
128
137
</tr>
129
138
<tr>
130
- <td align="center" style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${ textColor } ;">
139
+ <td align="center"
140
+ style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${ color . text } ;">
131
141
If you did not request this email you can safely ignore it.
132
142
</td>
133
143
</tr>
@@ -136,7 +146,7 @@ function html({ url, host, email }: Record<"url" | "host" | "email", string>) {
136
146
`
137
147
}
138
148
139
- // Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
140
- function text ( { url, host } : Record < " url" | " host" , string > ) {
149
+ /** Email Text body (fallback for email clients that don't render HTML, e.g. feature phones) */
150
+ function text ( { url, host } : { url : string ; host : string } ) {
141
151
return `Sign in to ${ host } \n${ url } \n\n`
142
152
}
1 commit comments
vercel[bot] commentedon Jul 5, 2022
Successfully deployed to the following URLs:
next-auth – ./
next-auth-nextauthjs.vercel.app
www.next-auth.js.org
next-auth-git-main-nextauthjs.vercel.app
next-auth.js.org
next-auth-phi-two.vercel.app