# Building with email/password authentication  Although Gadget provides a default email/password authentication method, there is a lot of flexibility to configure or build on top of the user emails sent through `emails` in the action context. ## Custom email templates  Whether you want to customize the copy and style of the emails you send on the `sendVerifyEmail`/`sendResetPassword` action or send any other transactional/marketing emails within your app, you can easily do so. 1. Create a constant value representing the desired custom email template (`CustomTemplate`) and declare an expression containing an HTML 5 document. 2. Structure this document however you want your email copy and style to be. 3. Now to render your custom template, when sending an email (via `emails.sendMail()`) within the `html` parameter you will pass the custom template like below: Create a constant value representing the desired custom email template (`CustomTemplate`) and declare an expression containing an HTML 5 document. Structure this document however you want your email copy and style to be. Now to render your custom template, when sending an email (via `emails.sendMail()`) within the `html` parameter you will pass the custom template like below: ```typescript export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, emails, }) => { if ( !record.emailVerified && record.emailVerificationToken && params.user?.emailVerificationCode ) { const url = new URL("/verify-email", Config.appUrl); url.searchParams.append("code", params.user?.emailVerificationCode); // create your custom email template const CustomTemplate = ` Email Verification

Email Verification

Click the button below to verify your email:

Click to Verify Email
`; await emails.sendMail({ to: record.email, subject: `Verify your email with ${Config.appName}`, // Pass your custom template // The default template is an EJS string html: DefaultEmailTemplates.renderEmailTemplate(CustomTemplate, { url: url.toString(), }), }); } }; ``` When passing the `CustomTemplate` through `emails.sendMail()`, note that there is no `text` parameter to pass as that is replaced by the content within your `CustomTemplate`. ### Email parameters  The `sendMail` function takes in a `MailData` object. The `MailData` object has the following params: | Name | Type | Description | | --- | --- | --- | | `to` | `string`, `Address`, `string[]`, `Address[]` | The address(es) that the email will be sent to. The `to` param can be an email address or `Address` object, or an array of either. For example: `john@smith.com` or `{ address: "john@smith.com", name: "John Smith" }` | | `from` | `string`, `Address` | The `from` address displayed to users as to who the email came from. This param is optional. If not provided, the `from` address will be set to `noreply@{your-app-slug}.gadget.app`. **Note**: When using the Gadget built-in email transport, you can only send mail from your Gadget app's domain. If your app uses a custom domain, mail will be sent from your `.gadget.app` address and the display name will be set to `noreply@{your-custom-domain}` so it appears to come from your custom domain. | | `cc` | `string`, `Address`,`string[]`, \`Address\[\]\`\` | The `cc` address(es) that the email will be sent to. The `cc` param can be an email address or `Address` object, or an array of either. For example: `john@smith.com` or `{ address: "john@smith.com", name: "John Smith" }` | | `bcc` | `string`, `Address`, `string[]`, `Address[]` | The `bcc` address(es) that the email will be sent to. The `bcc` param can be an email address or `Address` object, or an array of either. For example: `john@smith.com` or `{ address: "john@smith.com", name: "John Smith" }` | | `replyTo` | `string`, `Address`,`string[]`, `Address[]` | The `replyTo` address(es) to attach to the email. The `replyTo` param can be an email address or `Address` object. For example: `john@smith.com` or `{ address: "john@smith.com", name: "John Smith" }` | | `sender` | `string`, `Address` | The `sender` address used to identify the agent responsible for the actual transmission of the email. Can only be set when using a custom transport. | | `html` | `string` | The HTML body of the email. | | `text` | `string` | The plain text body of the email. | | `attachments` | `Attachment[]` | An array of files to be attached to the email. The `attachment` is an object with either a `path` param which represents the `string` path to the file, or a `content` param which represents the `Buffer` or `string` raw data of the file, and an optional `filename` param which represents the name of the file. If a filename is not provided, the original name of the file will be used. | | `inReplyTo` | `string` | The `inReplyTo` message ID property to set on the email. | | `references` | `string` | The `references` message ID property to set on the email. | | `headers` | `Record` | A list of custom headers to set on the email. | When using Gadget's built in email transport, you can only send emails from your app's domains, like `hello@your-app-slug.gadget.app`. If you need to send emails from different domains, you must set up your own email transport. ### Adding attachments to emails with GadgetMailer  Let's say we have a model called `csvFile` with a field `link` which is a `file` type and we want to dynamically create files to store in our model. To attach a generated csv file to an email we can do the following: ```typescript import { Base64 } from "base64-string"; import { DefaultEmailTemplates } from "gadget-server"; export const onSuccess: ActionOnSuccess = async ({ api, emails }) => { // 1. Create CSV content const csvContent = "name,age\njohn,32"; // 2. Encode the CSV content const enc = new Base64(); const b64 = enc.urlEncode(csvContent); // 3. Set the file name const csvFileName = `test.csv`; // 4. Create the file const file = await api.csvFile.create({ link: { base64: b64, fileName: csvFileName, }, }); const CustomTemplate = ` Email with CSV

Sending a CSV

See attachment

`; // 5. Send the email with the attachment await emails.sendMail({ to: "john@smith.com", subject: `Welcome`, html: DefaultEmailTemplates.renderEmailTemplate(CustomTemplate, {}), attachments: [ { filename: csvFileName, path: file.link?.url, }, ], }); }; ``` We can also fetch the file from a URL and pass it in as an attachment: ```typescript import pdf from "pdf-parse"; import { DefaultEmailTemplates } from "gadget-server"; const getPdfBuffer = async (url: string) => { try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const arrayBuffer = await response.arrayBuffer(); return Buffer.from(arrayBuffer); } catch (error) { console.error("Error fetching PDF:", error); throw error; } }; export const onSuccess: ActionOnSuccess = async ({ emails }) => { // 1. Fetch the PDF file from a URL const url = "https://example.com/example.pdf"; const pdfBuffer = await getPdfBuffer(url); const CustomTemplate = ` Email with PDF

Sending a PDF

See attachment

`; // 2. Send the email with the attachment await emails.sendMail({ to: "john@smith.com", subject: `Welcome`, html: DefaultEmailTemplates.renderEmailTemplate(CustomTemplate, {}), attachments: [ { filename: "myPdf.pdf", content: pdfBuffer, }, ], }); }; ``` ## Setting up an external transporter  If you decide to use an external email transport like Amazon SES, SendGrid, or Mailgun, you can configure that within Gadget. If the same credentials apply to every email in your app, [create a boot file](https://docs.gadget.dev/guides/http-routes/route-structure#boot-plugins) and declare the transport there. Use [environment variables](https://docs.gadget.dev/guides/development-tools/environment-variables) to store the credentials. If each tenant has its own credentials, set the transport inside the action just before you send the email. The parameters passed to `setTransport()` depend on the [type of transport](https://nodemailer.com/transports/) you want to use. Refer to the Nodemailer or external transport service documentation for the exact configuration fields. For example, if each Shopify shop stores its own SMTP credentials in an `emailSettings` model linked to `shopifyShop`, you can look up the current shop with `connections.shopify.currentShopId`, load that shop's settings, and configure the transport for that request: ```typescript export const run: ActionRun = async ({ api, connections, emails }) => { const shopId = connections.shopify.currentShopId; if (!shopId) { throw new Error("This action must run in the context of a Shopify shop"); } const emailSettings = await api.emailSettings.findFirst({ filter: { shopId: { equals: shopId }, }, select: { smtpHost: true, smtpPort: true, smtpUsername: true, smtpPassword: true, fromEmail: true, }, }); if (!emailSettings) { v; throw new Error("No email settings found for this shop"); } emails.setTransport({ host: emailSettings.smtpHost, port: emailSettings.smtpPort, auth: { user: emailSettings.smtpUsername, pass: emailSettings.smtpPassword, }, }); await emails.sendMail({ to: "customer@example.com", from: emailSettings.fromEmail, subject: "Your order is confirmed", html: "

Thanks for your order.

", }); }; ``` In this example, the `shopId` comes from the current Shopify session, so Gadget automatically gives you the tenant identifier for the merchant making the request. If you are not using Shopify, use the tenant identifier available in your own session, route params, or action params and query your settings model the same way. In summary to configure an external transporter: * Store your transporter credentials in one place, either globally or in a tenant-specific model. * Resolve the correct tenant identifier before sending the email. * Load the transport configuration and pass it to `emails.setTransport()`. * Call `emails.sendMail()` after the transport has been configured.