@syncello/auth 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -997,36 +997,140 @@ function escapeHtml(text) {
997
997
  }
998
998
 
999
999
  // src/core/email-template.ts
1000
- function generateEmailTemplate(options) {
1001
- const {
1002
- heading,
1003
- body,
1004
- buttonText,
1005
- buttonUrl,
1006
- welcomeMessage,
1007
- securityWarning,
1008
- footerNote,
1009
- appUrl,
1010
- marketingUrl,
1011
- appName = "App",
1012
- buttonBgColor = "#00fe9a",
1013
- buttonTextColor = "#000004",
1014
- buttonFontWeight = 500,
1015
- buttonRadius = "6px",
1016
- logoUrl,
1017
- footerColor = "#1e1b4b",
1018
- headingColor = "#1e1b4b"
1019
- } = options;
1020
- const safeAppName = escapeHtml(appName);
1021
- const safeButtonUrl = buttonUrl ? sanitizeEmailUrl(buttonUrl, appUrl) : "";
1022
- const safeAppUrl = appUrl ? sanitizeEmailUrl(appUrl, appUrl) : "";
1023
- const safeMarketingUrl = marketingUrl ? sanitizeEmailUrl(marketingUrl, marketingUrl).replace(/\/$/, "") : "";
1024
- const safeLogoUrl = logoUrl ? sanitizeEmailUrl(logoUrl, appUrl) : `${safeAppUrl.replace(/\/$/, "")}/email-header-image.png`;
1000
+ var FONT = "'Montserrat',Arial,sans-serif";
1001
+ var PAGE = "#f4f5f7";
1002
+ var BODY_TEXT = "#404040";
1003
+ var MUTED = "#737373";
1004
+ var RULE = "#e5e7eb";
1005
+ var TABLE = 'width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;"';
1006
+ function logoDimensions(width, height) {
1007
+ if (width === void 0 && height === void 0) return null;
1008
+ if (width === void 0 || height === void 0) {
1009
+ throw new Error("logoWidth and logoHeight must be given together");
1010
+ }
1011
+ return { width: pixels("logoWidth", width), height: pixels("logoHeight", height) };
1012
+ }
1013
+ function pixels(name, value) {
1014
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
1015
+ throw new Error(`${name} must be a positive integer`);
1016
+ }
1017
+ return escapeHtml(String(value));
1018
+ }
1019
+ function brandColours(branding) {
1020
+ return {
1021
+ buttonBgColor: escapeHtml(branding.buttonBgColor ?? "#00fe9a"),
1022
+ buttonTextColor: escapeHtml(branding.buttonTextColor ?? "#000004"),
1023
+ buttonFontWeight: escapeHtml(String(branding.buttonFontWeight ?? 500)),
1024
+ buttonRadius: escapeHtml(branding.buttonRadius ?? "6px"),
1025
+ footerColor: escapeHtml(branding.footerColor ?? "#1e1b4b"),
1026
+ headingColor: escapeHtml(branding.headingColor ?? "#1e1b4b")
1027
+ };
1028
+ }
1029
+ function logoUrlFor(logoUrl, appUrl) {
1030
+ if (logoUrl) return escapeHtml(sanitizeEmailUrl(logoUrl, appUrl));
1031
+ const base = appUrl ? sanitizeEmailUrl(appUrl, appUrl) : "";
1032
+ return escapeHtml(`${base.replace(/\/$/, "")}/email-header-image.png`);
1033
+ }
1034
+ function marketingUrlFor(marketingUrl) {
1035
+ return marketingUrl ? escapeHtml(sanitizeEmailUrl(marketingUrl, marketingUrl).replace(/\/$/, "")) : "";
1036
+ }
1037
+ var cardRow = (padding, content) => `
1038
+ <table ${TABLE}>
1039
+ <tr>
1040
+ <td class="pad" style="${padding}">
1041
+ ${content}
1042
+ </td>
1043
+ </tr>
1044
+ </table>`;
1045
+ function logoBlock({ logoUrl, logoSize, appName }) {
1046
+ const img = logoSize ? `<img src="${logoUrl}" width="${logoSize.width}" height="${logoSize.height}" style="width: ${logoSize.width}px; max-width: 100%; height: auto; display: block; border: 0;" alt="${appName}" title="${appName}">` : `<div class="fullWidth" style="max-width: 50%;">
1047
+ <img src="${logoUrl}" style="display: block; height: auto; border: 0; width: 100%;" alt="${appName}" title="${appName}" height="auto">
1048
+ </div>`;
1049
+ return `
1050
+ <!-- Header Image -->
1051
+ <table class="image_block" ${TABLE}>
1052
+ <tr>
1053
+ <td class="pad" style="padding-top:40px;padding-left:40px;padding-right:40px;padding-bottom:8px;">
1054
+ <div class="alignment" align="center">
1055
+ ${img}
1056
+ </div>
1057
+ </td>
1058
+ </tr>
1059
+ </table>`;
1060
+ }
1061
+ function buttonBlock({ label, url }, brand) {
1062
+ const font = `font-family: ${FONT}; font-size: 16px; font-weight: ${brand.buttonFontWeight};`;
1063
+ return `
1064
+ <!-- Button -->${cardRow(
1065
+ "padding-top:8px;padding-bottom:32px;padding-left:40px;padding-right:40px;text-align:center;",
1066
+ `<div class="alignment" align="center">
1067
+ <!--[if mso]>
1068
+ <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${url}" style="height:48px;width:auto;v-text-anchor:middle;" arcsize="12%" fillcolor="${brand.buttonBgColor}">
1069
+ <v:stroke dashstyle="Solid" weight="0px" color="${brand.buttonBgColor}"/>
1070
+ <w:anchorlock/>
1071
+ <v:textbox inset="0px,0px,0px,0px">
1072
+ <center dir="false" style="color:${brand.buttonTextColor};${font}">
1073
+ <![endif]-->
1074
+ <a href="${url}" style="background-color: ${brand.buttonBgColor}; border: 0px solid transparent; border-radius: ${brand.buttonRadius}; color: ${brand.buttonTextColor}; display: inline-block; ${font} line-height: 24px; mso-border-alt: none; padding: 12px 28px; text-align: center; text-decoration: none; word-break: keep-all;">${label}</a>
1075
+ <!--[if mso]>
1076
+ </center>
1077
+ </v:textbox>
1078
+ </v:roundrect>
1079
+ <![endif]-->
1080
+ </div>`
1081
+ )}
1082
+
1083
+ <!-- Copy Link -->${cardRow(
1084
+ "padding-bottom:24px;padding-left:40px;padding-right:40px;",
1085
+ `<div style="border-top: 1px solid ${RULE}; padding-top: 16px; color:${MUTED};font-family:${FONT};font-size:12px;line-height:1.5;text-align:left;mso-line-height-alt:18px;">
1086
+ <p style="margin: 0 0 4px 0; word-break: break-word;">Or copy and paste this link into your browser:</p>
1087
+ <a href="${url}" style="color:${MUTED};text-decoration:underline;word-break:break-all;">${url}</a>
1088
+ </div>`
1089
+ )}`;
1090
+ }
1091
+ function renderLayout(parts) {
1092
+ const { brand } = parts;
1093
+ const welcome = parts.welcome ? `
1094
+ <!-- Welcome Message -->${cardRow(
1095
+ "padding-top:24px;padding-left:40px;padding-right:40px;",
1096
+ `<div style="color:${brand.headingColor};font-family:${FONT};font-size:20px;font-weight:600;line-height:1.4;text-align:left;mso-line-height-alt:28px;">
1097
+ <p style="margin: 0; word-break: break-word;">${parts.welcome}</p>
1098
+ </div>`
1099
+ )}` : "";
1100
+ const heading = parts.heading ? `
1101
+ <!-- Heading -->${cardRow(
1102
+ "padding-top:24px;padding-left:40px;padding-right:40px;",
1103
+ `<div style="color:${brand.headingColor};font-family:${FONT};font-size:24px;font-weight:700;line-height:1.3;text-align:left;mso-line-height-alt:31px;">
1104
+ <p style="margin: 0; word-break: break-word;">${parts.heading}</p>
1105
+ </div>`
1106
+ )}` : "";
1107
+ const notice = parts.notice ? `
1108
+ <!-- Notice -->${cardRow(
1109
+ "padding-bottom:24px;padding-left:40px;padding-right:40px;",
1110
+ `<table ${TABLE.replace('style="', `style="background-color: ${PAGE}; border-left: 3px solid ${brand.headingColor}; border-radius: 4px; `)}>
1111
+ <tr>
1112
+ <td style="padding: 14px 16px; color: ${BODY_TEXT}; font-family: ${FONT}; font-size: 13px; line-height: 1.5; text-align: left;">
1113
+ ${parts.notice}
1114
+ </td>
1115
+ </tr>
1116
+ </table>`
1117
+ )}` : "";
1118
+ const footerNote = parts.footerNote ? `
1119
+ <!-- Footer Note -->${cardRow(
1120
+ "padding-bottom:32px;padding-left:40px;padding-right:40px;",
1121
+ `<div style="color:${MUTED};font-family:${FONT};font-size:13px;line-height:1.5;text-align:left;mso-line-height-alt:20px;">
1122
+ <p style="margin: 0; word-break: break-word;">${parts.footerNote}</p>
1123
+ </div>`
1124
+ )}` : "";
1125
+ const links = parts.marketingUrl ? `
1126
+ <p style="margin: 0 0 12px 0; word-break: break-word;">
1127
+ <a href="${parts.marketingUrl}/privacy" style="color: #ffffff; text-decoration: underline;">Privacy Policy</a> &nbsp;|&nbsp; <a href="${parts.marketingUrl}/contact" style="color: #ffffff; text-decoration: underline;">Get in Touch</a>
1128
+ </p>` : "";
1025
1129
  return `
1026
1130
  <!DOCTYPE html>
1027
1131
  <html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" lang="en">
1028
1132
  <head>
1029
- <title>${heading}</title>
1133
+ <title>${parts.title}</title>
1030
1134
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
1031
1135
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1032
1136
  <!--[if mso]>
@@ -1041,7 +1145,7 @@ function generateEmailTemplate(options) {
1041
1145
  </xml>
1042
1146
  <![endif]-->
1043
1147
  <!--[if !mso]><!-->
1044
- <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap" rel="stylesheet" type="text/css">
1148
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet" type="text/css">
1045
1149
  <!--<![endif]-->
1046
1150
  <style>
1047
1151
  * {
@@ -1062,13 +1166,6 @@ function generateEmailTemplate(options) {
1062
1166
  p {
1063
1167
  line-height: inherit;
1064
1168
  }
1065
- .desktop_hide,
1066
- .desktop_hide table {
1067
- mso-hide: all;
1068
- display: none;
1069
- max-height: 0px;
1070
- overflow: hidden;
1071
- }
1072
1169
  .image_block img+div {
1073
1170
  display: none;
1074
1171
  }
@@ -1077,41 +1174,16 @@ function generateEmailTemplate(options) {
1077
1174
  line-height: 0;
1078
1175
  }
1079
1176
  @media (max-width:660px) {
1080
- .desktop_hide table.icons-inner {
1081
- display: inline-block !important;
1082
- }
1083
- .icons-inner {
1084
- text-align: center;
1085
- }
1086
- .icons-inner td {
1087
- margin: 0 auto;
1088
- }
1089
- .mobile_hide {
1090
- display: none;
1177
+ .page {
1178
+ padding: 16px 8px !important;
1091
1179
  }
1092
1180
  .row-content {
1093
1181
  width: 100% !important;
1094
1182
  max-width: 100% !important;
1095
1183
  }
1096
- .stack .column {
1097
- width: 100%;
1098
- display: block;
1099
- }
1100
- .mobile_hide {
1101
- min-height: 0;
1102
- max-height: 0;
1103
- max-width: 0;
1104
- overflow: hidden;
1105
- font-size: 0px;
1106
- }
1107
- .desktop_hide,
1108
- .desktop_hide table {
1109
- display: table !important;
1110
- max-height: none !important;
1111
- }
1112
1184
  .pad {
1113
- padding-left: 20px !important;
1114
- padding-right: 20px !important;
1185
+ padding-left: 24px !important;
1186
+ padding-right: 24px !important;
1115
1187
  }
1116
1188
  }
1117
1189
  </style>
@@ -1123,235 +1195,46 @@ function generateEmailTemplate(options) {
1123
1195
  </style>
1124
1196
  <![endif]-->
1125
1197
  </head>
1126
- <body class="body" style="background-color: #f8f9fa; margin: 0; padding: 0; -webkit-text-size-adjust: none; text-size-adjust: none;">
1127
- <table class="nl-container" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #f8f9fa;">
1198
+ <body class="body" style="background-color: ${PAGE}; margin: 0; padding: 0; -webkit-text-size-adjust: none; text-size-adjust: none;">
1199
+ <table class="nl-container" ${TABLE.replace('style="', `style="background-color: ${PAGE}; `)}>
1128
1200
  <tbody>
1129
1201
  <tr>
1130
- <td>
1131
- <!-- Main Content Row -->
1132
- <table class="row row-1" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
1202
+ <td class="page" style="padding: 32px 16px;">
1203
+ <!-- Card -->
1204
+ <table class="row-content" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #ffffff; border: 1px solid ${RULE}; border-radius: 12px; max-width: 640px; width: 100%; margin: 0 auto;">
1133
1205
  <tbody>
1134
1206
  <tr>
1135
- <td>
1136
- <table class="row-content stack" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #ffffff; color: #000000; max-width: 640px; width: 100%; margin: 0 auto;">
1137
- <tbody>
1138
- <tr>
1139
- <td class="column column-1" width="100%" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; font-weight: 400; text-align: left; vertical-align: top;">
1140
-
1141
- <!-- Top Spacer -->
1142
- <table class="divider_block block-1" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
1143
- <tr>
1144
- <td class="pad" style="padding-bottom:12px;padding-top:30px;">
1145
- <div class="alignment" align="center">
1146
- <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
1147
- <tr>
1148
- <td class="divider_inner" style="font-size: 1px; line-height: 1px; border-top: 0px solid #BBBBBB;">
1149
- <span style="word-break: break-word;">&#8202;</span>
1150
- </td>
1151
- </tr>
1152
- </table>
1153
- </div>
1154
- </td>
1155
- </tr>
1156
- </table>
1207
+ <td width="100%" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; text-align: left; vertical-align: top;">
1208
+ ${logoBlock(parts)}
1209
+ ${welcome}
1210
+ ${heading}
1211
+
1212
+ <!-- Body Text -->${cardRow(
1213
+ "padding-top:16px;padding-bottom:24px;padding-left:40px;padding-right:40px;",
1214
+ `<div style="color:${BODY_TEXT};font-family:${FONT};font-size:15px;line-height:1.6;text-align:left;mso-line-height-alt:24px;">
1215
+ ${parts.body}
1216
+ </div>`
1217
+ )}
1218
+ ${parts.button ? buttonBlock(parts.button, brand) : ""}
1219
+ ${notice}
1220
+ ${footerNote}
1157
1221
 
1158
- <!-- Header Image -->
1159
- <table class="image_block block-2" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
1160
- <tr>
1161
- <td class="pad" style="padding-left:40px;padding-right:40px;width:100%;">
1162
- <div class="alignment" align="center">
1163
- <div class="fullWidth" style="max-width: 50%;">
1164
- <img src="${safeLogoUrl}" style="display: block; height: auto; border: 0; width: 100%;" alt="${safeAppName}" title="${safeAppName}" height="auto">
1165
- </div>
1166
- </div>
1167
- </td>
1168
- </tr>
1169
- </table>
1170
-
1171
- ${welcomeMessage ? `
1172
- <!-- Welcome Message -->
1173
- <table class="paragraph_block" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
1174
- <tr>
1175
- <td class="pad" style="padding-top:30px;padding-left:40px;padding-right:40px;">
1176
- <div style="color:${headingColor};font-family:'Montserrat',Arial,sans-serif;font-size:22px;font-weight:600;line-height:1.4;text-align:center;mso-line-height-alt:31px;">
1177
- <p style="margin: 0; word-break: break-word;">${welcomeMessage}</p>
1178
- </div>
1179
- </td>
1180
- </tr>
1181
- </table>
1182
- ` : ""}
1183
-
1184
- ${heading ? `
1185
- <!-- Heading -->
1186
- <table class="paragraph_block block-4" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
1187
- <tr>
1188
- <td class="pad" style="padding-bottom:10px;padding-left:40px;padding-right:40px;padding-top:10px;">
1189
- <div style="color:${headingColor};font-family:'Montserrat',Arial,sans-serif;font-size:30px;font-weight:700;line-height:1.2;text-align:center;mso-line-height-alt:36px;">
1190
- <p style="margin: 0; word-break: break-word;">${heading}</p>
1191
- </div>
1192
- </td>
1193
- </tr>
1194
- </table>
1195
- ` : ""}
1196
-
1197
- <!-- Body Text -->
1198
- <table class="paragraph_block block-5" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
1199
- <tr>
1200
- <td class="pad" style="padding-bottom:30px;padding-left:40px;padding-right:40px;padding-top:30px;">
1201
- <div style="color:#404040;font-family:'Montserrat',Arial,sans-serif;font-size:15px;line-height:1.6;text-align:left;mso-line-height-alt:24px;">
1202
- <p style="margin: 0; word-break: break-word;">${body}</p>
1203
- </div>
1204
- </td>
1205
- </tr>
1206
- </table>
1207
-
1208
- ${safeButtonUrl ? `
1209
- <!-- Button -->
1210
- <table class="button_block block-6" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
1211
- <tr>
1212
- <td class="pad" style="padding-left:10px;padding-right:10px;padding-bottom:10px;text-align:center;">
1213
- <div class="alignment" align="center">
1214
- <!--[if mso]>
1215
- <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${safeButtonUrl}" style="height:52px;width:auto;v-text-anchor:middle;" arcsize="12%" fillcolor="${buttonBgColor}">
1216
- <v:stroke dashstyle="Solid" weight="0px" color="${buttonBgColor}"/>
1217
- <w:anchorlock/>
1218
- <v:textbox inset="0px,0px,0px,0px">
1219
- <center dir="false" style="color:${buttonTextColor};font-family:'Montserrat',Arial,sans-serif;font-size:16px;font-weight:${buttonFontWeight};">
1220
- <![endif]-->
1221
- <a href="${safeButtonUrl}" style="background-color: ${buttonBgColor}; border: 0px solid transparent; border-radius: ${buttonRadius}; color: ${buttonTextColor}; display: inline-block; font-family: 'Montserrat', Arial, sans-serif; font-size: 16px; font-weight: ${buttonFontWeight}; mso-border-alt: none; padding: 10px 20px; text-align: center; text-decoration: none; text-transform: capitalize; word-break: keep-all;">
1222
- ${buttonText}
1223
- </a>
1224
- <!--[if mso]>
1225
- </center>
1226
- </v:textbox>
1227
- </v:roundrect>
1228
- <![endif]-->
1229
- </div>
1230
- </td>
1231
- </tr>
1232
- </table>
1233
-
1234
- <!-- Copy Link -->
1235
- <table class="paragraph_block" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
1236
- <tr>
1237
- <td class="pad" style="padding-bottom:30px;padding-left:40px;padding-right:40px;">
1238
- <div style="color:#737373;font-family:'Montserrat',Arial,sans-serif;font-size:12px;line-height:1.5;text-align:center;mso-line-height-alt:18px;">
1239
- <p style="margin: 0 0 5px 0; word-break: break-word;">or copy and paste this link into your browser:</p>
1240
- <a href="${safeButtonUrl}" style="color:#4a90d9;text-decoration:underline;word-break:break-all;">${safeButtonUrl}</a>
1241
- </div>
1242
- </td>
1243
- </tr>
1244
- </table>
1245
- ` : ""}
1246
-
1247
- ${securityWarning ? `
1248
- <!-- Security Warning -->
1249
- <table class="paragraph_block" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
1250
- <tr>
1251
- <td class="pad" style="padding-left:40px;padding-right:40px;padding-bottom:20px;">
1252
- <div style="background-color: #a1f1ff; border-radius: 8px; padding: 16px; display: flex; align-items: center; justify-content: center;">
1253
- <p style="margin: 0; color: #01001b; font-family: 'Montserrat', Arial, sans-serif; font-size: 13px; line-height: 1.5; text-align: center;">
1254
- ${securityWarning}
1255
- </p>
1256
- </div>
1257
- </td>
1258
- </tr>
1259
- </table>
1260
- ` : ""}
1261
-
1262
- <!-- Footer Note -->
1263
- <table class="paragraph_block" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
1264
- <tr>
1265
- <td class="pad" style="padding-bottom:30px;padding-left:40px;padding-right:40px;">
1266
- <div style="color:#737373;font-family:'Montserrat',Arial,sans-serif;font-size:13px;line-height:1.5;text-align:center;mso-line-height-alt:20px;">
1267
- <p style="margin: 0; word-break: break-word;">${footerNote}</p>
1268
- </div>
1269
- </td>
1270
- </tr>
1271
- </table>
1272
-
1273
- </td>
1274
- </tr>
1275
- </tbody>
1276
- </table>
1277
1222
  </td>
1278
1223
  </tr>
1279
1224
  </tbody>
1280
1225
  </table>
1281
1226
 
1282
- <!-- Spacer Row -->
1283
- <table class="row row-2" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
1227
+ <!-- Footer -->
1228
+ <table class="row-content" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: ${brand.footerColor}; border-radius: 12px; max-width: 640px; width: 100%; margin: 16px auto 0 auto;">
1284
1229
  <tbody>
1285
1230
  <tr>
1286
- <td>
1287
- <table class="row-content stack" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #f8f9fa; color: #000000; max-width: 640px; width: 100%; margin: 0 auto;">
1288
- <tbody>
1289
- <tr>
1290
- <td class="column column-1" width="100%" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; font-weight: 400; text-align: left; padding-bottom: 5px; vertical-align: top;">
1291
- <table class="empty_block block-1" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
1292
- <tr>
1293
- <td class="pad">
1294
- <div></div>
1295
- </td>
1296
- </tr>
1297
- </table>
1298
- </td>
1299
- </tr>
1300
- </tbody>
1301
- </table>
1302
- </td>
1303
- </tr>
1304
- </tbody>
1305
- </table>
1306
-
1307
- <!-- Footer Row -->
1308
- <table class="row row-3" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
1309
- <tbody>
1310
- <tr>
1311
- <td>
1312
- <table class="row-content stack" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: ${footerColor}; color: #000000; max-width: 640px; width: 100%; margin: 0 auto;">
1313
- <tbody>
1314
- <tr>
1315
- <td class="column column-1" width="100%" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; font-weight: 400; text-align: left; vertical-align: top; padding: 20px;">
1316
-
1317
- ${safeMarketingUrl ? `
1318
- <!-- Policy Links -->
1319
- <table class="paragraph_block" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
1320
- <tr>
1321
- <td class="pad" style="padding-bottom:20px;padding-left:40px;padding-right:40px;padding-top:20px;">
1322
- <div style="color:#ffffff;font-family:'Montserrat',Arial,sans-serif;font-size:13px;line-height:1.5;text-align:center;mso-line-height-alt:20px;">
1323
- <p style="margin: 0; word-break: break-word;">
1324
- <a href="${safeMarketingUrl}/privacy" style="color: #ffffff; text-decoration: underline;">Privacy Policy</a> | <a href="${safeMarketingUrl}/contact" style="color: #ffffff; text-decoration: underline;">Get in Touch</a>
1325
- </p>
1326
- </div>
1327
- </td>
1328
- </tr>
1329
- </table>
1330
- ` : ""}
1331
-
1332
- <!-- Copyright & Disclaimer -->
1333
- <table class="paragraph_block block-1" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
1334
- <tr>
1335
- <td class="pad" style="padding-bottom:15px;padding-left:40px;padding-right:40px;">
1336
- <div style="color:#a3a3a3;font-family:'Montserrat',Arial,sans-serif;font-size:12px;line-height:1.5;text-align:center;mso-line-height-alt:18px;">
1337
- <p style="margin: 0 0 12px 0; word-break: break-word;">\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} ${safeAppName}. All rights reserved.</p>
1338
- <p style="margin: 0; font-size: 11px; line-height: 1.5; word-break: break-word;">
1339
- Please do not reply to this email as this address does not accept incoming messages. This service email contains essential information relating to your ${safeAppName} account.
1340
- </p>
1341
- </div>
1342
- </td>
1343
- </tr>
1344
- </table>
1345
-
1346
- </td>
1347
- </tr>
1348
- </tbody>
1349
- </table>
1231
+ <td class="pad" style="padding: 24px 40px; color:#a3a3a3; font-family:${FONT}; font-size:12px; line-height:1.5; text-align:center; mso-line-height-alt:18px;">${links}
1232
+ <p style="margin: 0 0 8px 0; word-break: break-word;">\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} ${parts.appName}. All rights reserved.</p>
1233
+ <p style="margin: 0; font-size: 11px; word-break: break-word;">Please do not reply to this email as this address does not accept incoming messages. This service email contains essential information relating to your ${parts.appName} account.</p>
1350
1234
  </td>
1351
1235
  </tr>
1352
1236
  </tbody>
1353
1237
  </table>
1354
-
1355
1238
  </td>
1356
1239
  </tr>
1357
1240
  </tbody>
@@ -1360,6 +1243,55 @@ function generateEmailTemplate(options) {
1360
1243
  </html>
1361
1244
  `;
1362
1245
  }
1246
+ function generateEmailTemplate(options) {
1247
+ const { appUrl, buttonUrl } = options;
1248
+ const safeButtonUrl = buttonUrl ? escapeHtml(sanitizeEmailUrl(buttonUrl, appUrl)) : "";
1249
+ return renderLayout({
1250
+ title: options.heading,
1251
+ logoUrl: logoUrlFor(options.logoUrl, appUrl),
1252
+ logoSize: logoDimensions(options.logoWidth, options.logoHeight),
1253
+ appName: escapeHtml(options.appName ?? "App"),
1254
+ welcome: options.welcomeMessage ?? "",
1255
+ heading: options.heading,
1256
+ body: options.body,
1257
+ button: safeButtonUrl ? { label: options.buttonText, url: safeButtonUrl } : null,
1258
+ notice: options.securityWarning?.trim() ?? "",
1259
+ footerNote: options.footerNote,
1260
+ marketingUrl: marketingUrlFor(options.marketingUrl),
1261
+ brand: brandColours(options)
1262
+ });
1263
+ }
1264
+ function renderEmail(content) {
1265
+ const { appUrl, branding = {} } = content;
1266
+ const link = (url) => escapeHtml(sanitizeEmailUrl(url, appUrl));
1267
+ const text = (value) => escapeHtml(value).replace(/\r?\n/g, "<br>");
1268
+ const brand = brandColours(branding);
1269
+ const paragraphs = content.paragraphs.map(
1270
+ (p) => `<p style="margin: 0 0 16px 0; word-break: break-word;">${text(p)}</p>`
1271
+ );
1272
+ const sections = (content.sections ?? []).map((section) => {
1273
+ const title = section.url ? `<a href="${link(section.url)}" style="color: ${brand.headingColor}; font-weight: 600; text-decoration: none;">${text(section.title)}</a>` : `<span style="color: ${brand.headingColor}; font-weight: 600;">${text(section.title)}</span>`;
1274
+ const rows = section.rows.map(
1275
+ (row) => `<li style="margin: 0 0 4px 0;">${row.url ? `<a href="${link(row.url)}" style="color: ${BODY_TEXT}; text-decoration: underline;">${text(row.text)}</a>` : text(row.text)}</li>`
1276
+ );
1277
+ return `<p style="margin: 8px 0 4px 0; word-break: break-word;">${title}</p>
1278
+ <ul style="margin: 0 0 16px 0; padding-left: 20px;">${rows.join("")}</ul>`;
1279
+ });
1280
+ return renderLayout({
1281
+ title: escapeHtml(content.heading),
1282
+ logoUrl: logoUrlFor(branding.logoUrl, appUrl),
1283
+ logoSize: logoDimensions(branding.logoWidth, branding.logoHeight),
1284
+ appName: escapeHtml(content.appName ?? "App"),
1285
+ welcome: "",
1286
+ heading: escapeHtml(content.heading),
1287
+ body: [...paragraphs, ...sections].join("\n "),
1288
+ button: content.button ? { label: escapeHtml(content.button.label), url: link(content.button.url) } : null,
1289
+ notice: content.notice ? text(content.notice) : "",
1290
+ footerNote: content.footerNote ? text(content.footerNote) : "",
1291
+ marketingUrl: marketingUrlFor(branding.marketingUrl),
1292
+ brand
1293
+ });
1294
+ }
1363
1295
 
1364
1296
  // src/core/passwordless.ts
1365
1297
  var PASSWORDLESS_TTL_MS = 10 * 60 * 1e3;
@@ -6040,6 +5972,7 @@ export {
6040
5972
  rateLimit,
6041
5973
  recentPasswordlessFailures,
6042
5974
  refreshSession,
5975
+ renderEmail,
6043
5976
  requestEmailChange,
6044
5977
  requireAuth,
6045
5978
  requireVerifiedEmail,