diff --git a/.github/ISSUE_TEMPLATE/2_bug_provider.yml b/.github/ISSUE_TEMPLATE/2_bug_provider.yml index 1ee4409a23..3bb065c82c 100644 --- a/.github/ISSUE_TEMPLATE/2_bug_provider.yml +++ b/.github/ISSUE_TEMPLATE/2_bug_provider.yml @@ -68,6 +68,7 @@ body: - "Logto" - "Loops" - "Mailchimp" + - "MailerSend" - "Mail.ru" - "Mastodon" - "Medium" diff --git a/docs/pages/getting-started/authentication/email.mdx b/docs/pages/getting-started/authentication/email.mdx index 7e0248efda..6062606764 100644 --- a/docs/pages/getting-started/authentication/email.mdx +++ b/docs/pages/getting-started/authentication/email.mdx @@ -89,6 +89,14 @@ This login mechanism starts by the user providing their email address at the log
Mailgun
+ + +
Mailersend
+
- + ```ts filename="/src/routes/plugin@auth.ts" import { QwikAuth$ } from "@auth/qwik" import Mailgun from "@auth/qwik/providers/mailgun" @@ -1340,6 +1348,183 @@ Start your application, once the user enters their Email and clicks on the signi For more information on this provider go to the [Mailgun docs page](/getting-started/providers/mailgun). + + + +### Mailersend Setup + + + +### Database Adapter + +Please make sure you've [setup a database adapter](/getting-started/database), as mentioned earlier, +a database is required for passwordless login to work as verification tokens need to be stored. + +### Setup Environment Variables + +Auth.js will automatically pick up these if formatted like the example above. +You can [also use a different name for the environment variables](/guides/environment-variables#oauth-variables) if needed, but then you'll need to pass them to the provider manually. + +```bash filename=".env" +AUTH_MAILERSEND_KEY=mlsn.123 +``` + +### Setup Provider + +Let's enable `Mailersend` as a sign in option in our Auth.js configuration. You'll have to import the `Mailersend` provider from the package and pass it to the providers array we setup earlier in the Auth.js config file: + + + + +```ts filename="./auth.ts" +import NextAuth from "next-auth" +import Mailersend from "next-auth/providers/mailersend" + +export const { handlers, auth, signIn, signOut } = NextAuth({ + providers: [Mailersend], +}) +``` + + + + +```ts filename="/src/routes/plugin@auth.ts" +import { QwikAuth$ } from "@auth/qwik" +import Mailersend from "@auth/qwik/providers/mailersend" + +export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$( + () => ({ + providers: [Mailersend], + }) +) +``` + + + + +```ts filename="./src/auth.ts" +import SvelteKitAuth from "@auth/sveltekit" +import Mailersend from "@auth/sveltekit/providers/mailersend" + +export const { handle, signIn, signOut } = SvelteKitAuth({ + providers: [Mailersend], +}) +``` + +```ts filename="./src/hooks.server.ts" +export { handle } from "./auth" +``` + + + + +### Add Signin Button + +Next, we can add a signin button somewhere in your application like the Navbar. This will send an email to the user containing the magic link to sign in. + + + + +```tsx filename="./components/sign-in.tsx" +import { signIn } from "../../auth.ts" + +export function SignIn() { + return ( +
{ + "use server" + await signIn("mailersend", formData) + }} + > + + +
+ ) +} +``` + +
+ + +```tsx filename="./components/sign-in.tsx" +"use client" +import { signIn } from "next-auth/react" + +export function SignIn() { + const mailersendAction = (formData: FormData) => { + signIn("mailersend", formData) + } + + return ( +
+ + +
+ ) +} +``` + +
+ + +```ts filename="./components/sign-in.tsx" +import { component$ } from "@builder.io/qwik" +import { useSignIn } from "./plugin@auth" + +export default component$(() => { + const signInSig = useSignIn() + + return ( + + ) +}) +``` + + + + +```html filename="src/routes/+page.svelte" + + +
+ +
+``` + +
+
+ +### Signin + +Start your application, once the user enters their Email and clicks on the signin button, they'll be redirected to a page that asks them to check their email. When they click on the link in their email, they will be signed in. + +
+ + + Check our [Customising magic links + emails](/getting-started/providers/mailersend#customization) to learn how to + change the look and feel of the emails the user receives to sign in. + + +For more information on this provider go to the [Mailersend docs page](/getting-started/providers/mailersend). +
diff --git a/docs/pages/getting-started/providers/mailersend.mdx b/docs/pages/getting-started/providers/mailersend.mdx new file mode 100644 index 0000000000..589d491084 --- /dev/null +++ b/docs/pages/getting-started/providers/mailersend.mdx @@ -0,0 +1,272 @@ +import { Callout, Tabs } from "nextra/components" +import { Code } from "@/components/Code" + + + +# Mailersend Provider + +## Overview + +The Mailersend provider uses email to send "magic links" that contain URLs with verification tokens can be used to sign in. + +Adding support for signing in via email in addition to one or more OAuth services provides a way for users to sign in if they lose access to their OAuth account (e.g. if it is locked or deleted). + +The Mailersend provider can be used in conjunction with (or instead of) one or more OAuth providers. + +### How it works + +On initial sign in, a **Verification Token** is sent to the email address provided. By default this token is valid for 24 hours. If the Verification Token is used within that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in. + +If someone provides the email address of an _existing account_ when signing in, an email is sent and they are signed into the account associated with that email address when they follow the link in the email. + + + The Mailersend provider can be used with both JSON Web Token and database + managed sessions, however **you must configure a database** to use it. It is + not possible to enable email sign in without using a database. + + +## Configuration + +1. First, you'll need to [add your domain](https://app.mailersend.com/domains) to your Mailersend account. This is required by Mailersend and this domain of the address you use in the `from` provider option. + +2. Next, you will have to generate an API key with sending access in the [Mailersend Dashboard](https://app.mailersend.com/api-tokens). You can save this API key as the `AUTH_MAILERSEND_KEY` environment variable. + +```sh +AUTH_MAILERSEND_KEY=abc +``` + +If you name your environment variable `AUTH_MAILERSEND_KEY`, the provider will pick it up automatically and your Auth.js configuration object can be simpler. If you'd like to rename it to something else, however, you'll have to manually pass it into the provider in your Auth.js configuration. + + + + +```ts filename="./auth.ts" +import NextAuth from "next-auth" +import Mailersend from "next-auth/providers/mailersend" + +export const { handlers, auth, signIn, signOut } = NextAuth({ + adapter: ..., + providers: [ + Mailersend({ + // If your environment variable is named differently than default + apiKey: MAILERSEND_MAGIC_LINKS_TOKEN, + from: "no-reply@company.com" + }), + ], +}) +``` + + + + +```ts filename="/src/routes/plugin@auth.ts" +import { QwikAuth$ } from "@auth/qwik" +import Mailersend from "@auth/qwik/providers/mailersend" + +export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$( + () => ({ + providers: [ + Mailersend({ + // If your environment variable is named differently than default + apiKey: import.meta.env.MAILERSEND_MAGIC_LINKS_TOKEN, + from: "no-reply@company.com", + }), + ], + }) +) +``` + + + + +```ts filename="./src/auth.ts" +import { SvelteKitAuth } from "@auth/sveltekit" +import Mailersend from "@auth/sveltekit/providers/mailersend" +import { env } from "$env/dynamic/prviate" + +export const { handle, signIn, signOut } = SvelteKitAuth({ + adapter: ..., + providers: [ + Mailersend({ + // If your environment variable is named differently than default + apiKey: env.MAILERSEND_MAGIC_LINKS_TOKEN, + from: "no-reply@company.com", + }), + ], +}) +``` + + + + +4. Do not forget to setup one of the [database adapters](https://authjs.dev/getting-started/database) for storing the Email verification token. + +5. You can now start the sign-in process with an email address at `/api/auth/signin`. + +A user account (i.e. an entry in the `Users` table) will not be created for the user until the first time they verify their email address. If an email address is already associated with an account, the user will be signed in to that account when they click the link in magic link email and use up the verification token. + +## Customization + +### Email Body + +You can fully customize the sign in email that is sent by passing a custom function as the `sendVerificationRequest` option to `Mailersend()`. + +```js {7} filename="./auth.ts" +import NextAuth from "next-auth" +import Mailersend from "next-auth/providers/mailersend" + +export const { handlers, auth, signIn, signOut } = NextAuth({ + providers: [ + Mailersend({ + server: process.env.EMAIL_SERVER, + from: process.env.EMAIL_FROM, + sendVerificationRequest({ + identifier: email, + url, + provider: { server, from }, + }) { + // your function + }, + }), + ], +}) +``` + +As an example, the following shows the source for our built-in `sendVerificationRequest()` method. Notice that we're rendering the HTML (`html()`) and making the network call (`fetch()`) to Mailersend to actually do the sending here in this method. + +```ts {4, 14} filename="./lib/authSendRequest.ts" +export async function sendVerificationRequest(params) { + const { identifier: to, provider, url, theme } = params + const { host } = new URL(url) + const res = await fetch("https://api.mailersend.com/v1/email", { + method: "POST", + headers: { + Authorization: `Bearer ${provider.apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + from: { email: provider.from }, + to: [{ email: to }], + subject: `Sign in to ${host}`, + html: html({ url, host, theme }), + text: text({ url, host }), + }), + }) + + if (!res.ok) throw new Error("Mailersend error: " + (await res.text())) +} + +function html(params: { url: string; host: string; theme: Theme }) { + const { url, host, theme } = params + + const escapedHost = host.replace(/\./g, "​.") + + const brandColor = theme.brandColor || "#346df1" + const color = { + background: "#f9f9f9", + text: "#444", + mainBackground: "#fff", + buttonBackground: brandColor, + buttonBorder: brandColor, + buttonText: theme.buttonText || "#fff", + } + + return ` + + + + + + + + + + + +
+ Sign in to ${escapedHost} +
+ + + + +
Sign + in
+
+ If you did not request this email you can safely ignore it. +
+ +` +} + +// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones) +function text({ url, host }: { url: string; host: string }) { + return `Sign in to ${host}\n${url}\n\n` +} +``` + + + If you want to generate great looking emails with React that are compatible + with many email clients, check out [mjml](https://mjml.io) or + [react-email](https://react.email) + + +### Verification Tokens + +By default, we are generating a random verification token. You can define a `generateVerificationToken` method in your provider options if you want to override it: + +```ts filename="./auth.ts" +import NextAuth from "next-auth" +import Mailersend from "next-auth/providers/mailersend" + +export const { handlers, auth, signIn, signOut } = NextAuth({ + providers: [ + Mailersend({ + async generateVerificationToken() { + return crypto.randomUUID() + }, + }), + ], +}) +``` + +### Normalizing Email Addresses + +By default, Auth.js will normalize the email address. It treats the address as case-insensitive (which is technically not compliant to the [RFC 2821 spec](https://datatracker.ietf.org/doc/html/rfc2821), but in practice this causes more problems than it solves, i.e. when looking up users by e-mail from databases.) and also removes any secondary email address that may have been passed in as a comma-separated list. You can apply your own normalization via the `normalizeIdentifier` method on the `Mailersend` provider. The following example shows the default behavior: + +```ts filename="./auth.ts" +import NextAuth from "next-auth" +import Mailersend from "next-auth/providers/mailersend" + +export const { handlers, auth, signIn, signOut } = NextAuth({ + providers: [ + Mailersend({ + normalizeIdentifier(identifier: string): string { + // Get the first two elements only, + // separated by `@` from user input. + let [local, domain] = identifier.toLowerCase().trim().split("@") + // The part before "@" can contain a "," + // but we remove it on the domain part + domain = domain.split(",")[0] + return `${local}@${domain}` + + // You can also throw an error, which will redirect the user + // to the sign-in page with error=EmailSignin in the URL + // if (identifier.split("@").length > 2) { + // throw new Error("Only one email allowed") + // } + }, + }), + ], +}) +``` + + + Always make sure this returns a single e-mail address, even if multiple ones + were passed in. + diff --git a/docs/public/img/providers/mailersend.svg b/docs/public/img/providers/mailersend.svg new file mode 100644 index 0000000000..ed67c34a1c --- /dev/null +++ b/docs/public/img/providers/mailersend.svg @@ -0,0 +1,12 @@ + + + symbol + + diff --git a/packages/core/src/providers/mailersend.ts b/packages/core/src/providers/mailersend.ts new file mode 100644 index 0000000000..b81aeddddd --- /dev/null +++ b/packages/core/src/providers/mailersend.ts @@ -0,0 +1,32 @@ +import type { EmailConfig, EmailUserConfig } from "./index.js" +import { html, text } from "../lib/utils/email.js" + +export default function Mailersend(config: EmailUserConfig): EmailConfig { + return { + id: "mailersend", + type: "email", + name: "Mailersend", + from: "no-reply@authjs.dev", + maxAge: 24 * 60 * 60, + async sendVerificationRequest(params) { + const { identifier: to, provider, url, theme } = params + const { host } = new URL(url) + const res = await fetch("https://api.mailersend.com/v1/email", { + method: "POST", + headers: { + Authorization: `Bearer ${provider.apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + from: { email: provider.from }, + to: [{ email: to }], + subject: `Sign in to ${host}`, + html: html({ url, host, theme }), + text: text({ url, host }), + }), + }) + if (!res.ok) throw new Error("Mailersend error: " + (await res.text())) + }, + options: config, + } +}