PageSourceSearch

https://login.mailgun.com/login/

html mailgun.com collected 2026-09-24 06:50:24 UTC 40,248 bytes, 976 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5  <title>Login | Mailgun</title>
6
7  <meta charset="utf-8" />
8  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
9  <meta name="description" content="Sign into your Mailgun account. Mailgun
10      provides powerful APIs so you can send, receive, and track email
11      effortlessly." />
12  <meta name="viewport" content="width=device-width, initial-scale=1" />
13
14  <!-- Data Layer -->
15<script>
16window.dataLayer = window.dataLayer || [];
17</script>
17
18
19<!-- Google Tag Manager -->
20
21<script>
vendor: 328 bytes, lines 21-26
21
22(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
23new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
24j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
25'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
26})(window,document,'script','dataLayer','
26GTM-WN2J6
vendor: 4 bytes, line 26
26');
27</script>
27
28
29<script>
vendor: 328 bytes, lines 29-34
29
30(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
31new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
32j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
33'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
34})(window,document,'script','dataLayer','
34GTM-K833FLHN
vendor: 4 bytes, line 34
34');
35</script>
35
36
37
38<!-- Page view data layer -->
39<script>
40window.dataLayer = window.dataLayer || [];
41
42window.pushGtmPageView = function() {
43  if (window.gtmPageViewPushed) {
44    return;
45  }
46  window.gtmPageViewPushed = true;
47  clearTimeout(window.gtmPageViewTimeout);
48
49  var rudderstackId = "";
50  if (window.rudderanalytics && rudderanalytics.getAnonymousId) {
51    rudderstackId = rudderanalytics.getAnonymousId();
52  }
53
54  window.dataLayer.push({
55    event: "page_view",
56    page_info: {
57      page_url: window.location.href,
58      page_path: window.location.pathname,
59      page_title: document.title,
60      page_referrer: document.referrer,
61      portal: "mailgun",
62      platform: "app",
63      language: (navigator.language || "").toLowerCase(),
64      business_country: "us",
65      business_region: "na",
66      environment: "prod",
67      timezone: "utc-5",
68    },
69    user_info: {
70      logged_in: false,
71      // Only relevant when the user's remember-me cookie is set.
72      internal_traffic: false,
73      user_id: "",
74      rudderstack_id: rudderstackId,
75    },
76  });
77};
78
79// Do not block page_view indefinitely if RudderStack is slow or blocked.
80window.gtmPageViewTimeout = setTimeout(function() {
81  window.pushGtmPageView();
82}, 1000);
83</script>
83
84
85<!-- Rudderstack -->
86<script>
87!function(){"use strict";window.RudderSnippetVersion="3.2.0";var e="rudderanalytics";window[e]||(window[e]=[])
88;var rudderanalytics=window[e];if(Array.isArray(rudderanalytics)){
89if(true===rudderanalytics.snippetExecuted&&window.console&&console.error){
90console.error("RudderStack JavaScript SDK snippet included more than once.")}else{rudderanalytics.snippetExecuted=true,
91window.rudderAnalyticsBuildType="legacy";var sdkBaseUrl="https://cdn.rudderlabs.com";var sdkVersion="v3"
92;var sdkFileName="rsa.min.js";var scriptLoadingMode="async"
93;var r=["setDefaultInstanceKey","load","ready","page","track","identify","alias","group","reset","setAnonymousId","startSession","endSession","consent","addCustomIntegration"]
94;for(var n=0;n<r.length;n++){var t=r[n];rudderanalytics[t]=function(r){return function(){var n
95;Array.isArray(window[e])?rudderanalytics.push([r].concat(Array.prototype.slice.call(arguments))):null===(n=window[e][r])||void 0===n||n.apply(window[e],arguments)
96}}(t)}try{
97new Function('class Test{field=()=>{};test({prop=[]}={}){return prop?(prop?.property??[...prop]):import("");}}'),
98window.rudderAnalyticsBuildType="modern"}catch(i){}var d=document.head||document.getElementsByTagName("head")[0]
99;var o=document.body||document.getElementsByTagName("body")[0];window.rudderAnalyticsAddScript=function(e,r,n){
100var t=document.createElement("script");t.src=e,t.setAttribute("data-loader","RS_JS_SDK"),r&&n&&t.setAttribute(r,n),
101"async"===scriptLoadingMode?t.async=true:"defer"===scriptLoadingMode&&(t.defer=true),
102d?d.insertBefore(t,d.firstChild):o.insertBefore(t,o.firstChild)},window.rudderAnalyticsMount=function(){!function(){
103if("undefined"==typeof globalThis){var e;var r=function getGlobal(){
104return"undefined"!=typeof self?self:"undefined"!=typeof window?window:null}();r&&Object.defineProperty(r,"globalThis",{
105value:r,configurable:true})}
106}(),window.rudderAnalyticsAddScript("".concat(sdkBaseUrl,"/").concat(sdkVersion,"/").concat(window.rudderAnalyticsBuildType,"/").concat(sdkFileName),"data-rsa-write-key","1rAOS5TDzgx9T39bG58kwN5ZUQz")
107},
108"undefined"==typeof Promise||"undefined"==typeof globalThis?window.rudderAnalyticsAddScript("https://polyfill-fastly.io/v3/polyfill.min.js?version=3.111.0&features=Symbol%2CPromise&callback=rudderAnalyticsMount"):window.rudderAnalyticsMount()
109;
110var loadOptions = {};
111rudderanalytics.load("1rAOS5TDzgx9T39bG58kwN5ZUQz", "https://mailgun-dataplane.rudderstack.com", loadOptions);
112rudderanalytics.ready(function() {
113  window.pushGtmPageView();
114});}}}();
115</script>
115
116
117<!-- Global styles -->
118<link href="/login/static/styles/sinch_login.css?v=1.0.0" rel="stylesheet" />
119
120<!-- Favicon -->
121<link href="/login/static/favicon.png" rel="icon" type="image/png" />
122
123<script>
124  window.sentryOnLoad = function () {
125    // add ?sentry_debug=true to enable debugging
126    const params = new URLSearchParams(window.location.search);
127    const debugKey = 'sentry_debug';
128    if (params.get(debugKey) == 'true') {
129      localStorage.setItem(debugKey, true)
130    } else if (params.get(debugKey) == 'false') {
131      localStorage.removeItem(debugKey)
132    }
133
134    const environment = "mf-use4";
135    const enabled = environment !== 'local';
136
137    Sentry.init({
138      debug: !!localStorage.getItem(debugKey),
139      release: "[email protected]",
140      enabled,
141      integrations: [
142        Sentry.browserTracingIntegration(),
143      ],
144      environment,
145      sampleRate: 0.02,
146      tracesSampleRate: 0.02,
147      sendDefaultPii: false,
148    });
149
150    if (enabled) {
151      Sentry.lazyLoadIntegration("captureConsoleIntegration").then(
152        (integration) => {
153          Sentry.addIntegration(integration({
154            levels: ['error']
155          }));
156        },
157      );
158    }
159  };
160</script>
160
161
162
163<!--
164  Nectary Bootstrapping
165
166  Subresource Integrity hash generated from https://www.srihash.org/
167  or manually with:
168  ```
169  openssl dgst -sha384 -binary FILENAME.js | openssl base64 -A
170  ```
171-->
172<!-- Sourced from https://esm.sh/v135/@nectary/[email protected]/es2022/standalone.js -->
173<script async crossorigin="anonymous"
174  integrity="sha384-fuTBUHu7CloEMNY2h49ryLPrgM3sSTLJubW9lBwXSImr9Wbdzwn7bntzb3XlqVnO"
175  src="/login/static/scripts/nectary.js?v=4.7.2" type="module"></script>
175
176
177<!-- Sourced from https://esm.sh/v135/@nectary/[email protected]/es2022/theme-base.css -->
178<link crossorigin="anonymous" href="/login/static/styles/nectary-theme-base.css?v=1.4.1"
179  integrity="sha384-xOYujAaF/C7tR9qk3nLSPWjIdPE0nSjXzgzTdE0P8HqJ17/90cxPRq4HokDzo/jC" rel="stylesheet" />
180
181<!-- Sourced from https://esm.sh/v135/@nectary/[email protected]/es2022/theme-cpaas-base.css -->
182<link crossorigin="anonymous" href="/login/static/styles/nectary-theme-cpaas.css?v=1.1.0"
183  integrity="sha384-Z+tQehtkYAPOWqpubvCM7JNSjFamtQ+zzqVZtPuRJsxJU4nYOxEMxngYaus/X953" rel="stylesheet" />
184
185<!-- Sourced from https://esm.sh/v135/@nectary/[email protected]/es2022/theme-cpaas-mailgun.css -->
186<!-- SOME manually hacked fixes are included in this file -->
187<link crossorigin="anonymous" href="/login/static/styles/nectary-theme-cpaas-mailgun.css?v=1.1.0"
188  integrity="sha384-vac6Iuz/BxN/TLZns4FWC1hSkfYnq1hW08UWd07mgMmEFc5Zpso2nkvEpkNFQjGt" rel="stylesheet" />
189
190
191<!-- Umbrella.JS - Lightweight DOM utils -->
192<script crossorigin="anonymous" defer
193  integrity="sha384-Gi29SVoexXQhiEdmsWbF75honkW88QXma0jWL0AmI9qGPWmH6pkGDCJ9JY7Q7A2U"
194  src="/login/static/scripts/umbrella.min.js"></script>
194
195
196<!-- QRious.JS - QR code utils -->
197<script crossorigin="anonymous" defer
198  integrity="sha384-V13uVxWWqYBo65PM66jXr5pqe5n8gF9TYuHicOojbpaPxSVZWI3oxVsqgmK+LofD"
199  src="/login/static/scripts/qrious.js"></script>
199
200
201<script src="/login/static/scripts/sentry.js"
202integrity="sha384-C8r+KzJLGPkV7u52rhGw6CkoWv+sDYhm/fl6DToVroTSQPbNQprUNR2E0iFpWB68"
203crossorigin="anonymous"></script>
203
204
205  
205<script>
206    // global function to handle state management for sinch-checkbox
207    function manageCheckbox(checkboxId) {
208      const checkbox = document.getElementById(checkboxId)
209      checkbox.addEventListener('click', () => {
210        const checkedValue = !checkbox.getAttribute('checked')
211        checkbox.setAttribute('checked', checkedValue)
212      })
213    }
214
215    // global function to show error/success messages
216    function showMessage(error, type = 'error', locationId = 'loginError') {
217      document.body.classList.add(type)
218      const loginErrorElement = document.getElementById(locationId)
219      loginErrorElement.innerHTML = `<img src="/login/static/${type}.svg" /> <span class="loginErrorText">${error}</span>`;
220      loginErrorElement.classList.remove('hidden')
221    }
222
223    function hideMessage() {
224      const loginErrorElement = document.getElementById('loginError')
225      loginErrorElement.innerHTML = '';
226      loginErrorElement.classList.add('hidden')
227    }
228
229    function copyTextListener(copyButtonId, textId) {
230      document.getElementById(copyButtonId).addEventListener('-click', () => {
231        const recoveryCodeEl = document.getElementById(textId)
232        navigator.clipboard.writeText(recoveryCodeEl.innerText);
233        const range = document.createRange();
234        range.selectNodeContents(recoveryCodeEl);
235
236        const selection = window.getSelection();
237        selection.removeAllRanges();
238        selection.addRange(range);
239      })
240    }
241  </script>
241
242</head>
243
244<body class="nectary-theme-base cpaas-theme-base cpaas-theme-mailgun">
245  <!-- Google Tag Manager (noscript) -->
vendor: 78 bytes, lines 245-248
245
246  
247  <noscript>
248    <iframe src="https://www.googletagmanager.com/ns.html?id=
248GTM-WN2J6
vendor: 172 bytes, lines 248-253
248" height="0" width="0"
249      style="display: none; visibility: hidden"></iframe>
250  </noscript>
251  
252  <noscript>
253    <iframe src="https://www.googletagmanager.com/ns.html?id=
253GTM-K833FLHN
vendor: 101 bytes, lines 253-258
253" height="0" width="0"
254      style="display: none; visibility: hidden"></iframe>
255  </noscript>
256  
257
258  
258<main>
259    <section>
260      <svg class="sinch-mailgun-wordmark" viewBox="0 0 430 80" fill="none" xmlns="http://www.w3.org/2000/svg">
261        <g clip-path="url(#clip0_2232_6547)">
262        <path d="M231.65 55.09V27.7132H237.501V30.4171C239.267 28.3194 242.027 27.1613 245.284 27.1613C249.204 27.1613 252.349 28.7613 254.171 31.8528C256.269 28.9272 259.8 27.1613 263.886 27.1613C270.619 27.1613 275.31 31.9086 275.31 39.3597V55.09H269.183V39.9117C269.183 35.552 266.81 32.9566 262.892 32.9566C258.974 32.9566 256.545 35.6605 256.545 39.9117V55.09H250.417V39.9117C250.417 35.552 248.044 32.9566 244.18 32.9566C240.317 32.9566 237.777 35.6605 237.777 39.9117V55.09H231.65Z" fill="black"/>
263        <path d="M279.061 41.4031C279.061 33.4 285.022 27.1628 292.859 27.1628C296.723 27.1628 299.979 28.7085 302.022 31.3582V27.7147H307.873V55.0915H302.022V51.4481C300.035 54.0977 296.723 55.6434 292.859 55.6434C285.022 55.6434 279.061 49.4062 279.061 41.4031ZM301.802 41.4031C301.802 36.4915 298.325 32.9039 293.523 32.9039C288.721 32.9039 285.188 36.4915 285.188 41.4031C285.188 46.3147 288.72 49.8481 293.523 49.8481C298.326 49.8481 301.802 46.3163 301.802 41.4031Z" fill="black"/>
264        <path d="M314.495 19.9318C314.495 17.8899 315.985 16.4 318.027 16.4C320.069 16.4 321.558 17.8899 321.558 19.9318C321.558 21.9737 320.069 23.4093 318.027 23.4093C315.985 23.4093 314.495 21.9194 314.495 19.9318ZM314.937 55.09V27.7132H321.064V55.09H314.937Z" fill="black"/>
265        <path d="M328.072 55.09V17.0063H334.199V55.09H328.072Z" fill="black"/>
266        <path d="M339.904 59.0094L345.424 56.4156C346.638 59.2853 349.067 60.9969 353.593 60.9969C358.56 60.9969 361.816 58.4574 361.816 53.214V50.9504C359.774 53.4342 356.572 54.9241 352.819 54.9241C344.872 54.9241 339.075 48.8528 339.075 41.0156C339.075 33.1783 345.256 27.1613 352.873 27.1613C356.517 27.1613 359.772 28.4853 361.814 31.0807V27.7132H367.72V53.1024C367.72 62.2094 361.096 66.459 353.535 66.459C346.968 66.459 342.276 63.7551 339.903 59.0078L339.904 59.0094ZM361.54 41.0156C361.54 36.2683 358.118 32.7923 353.371 32.7923C348.624 32.7923 345.202 36.2698 345.202 41.0156C345.202 45.7613 348.624 49.2388 353.371 49.2388C358.118 49.2388 361.54 45.7613 361.54 41.0156Z" fill="black"/>
267        <path d="M373.185 43.2792V27.7148H379.312V42.7272C379.312 47.1985 381.686 49.8466 385.603 49.8466C389.521 49.8466 392.171 47.0869 392.171 42.7272V27.7148H398.298V55.0916H392.337V52.442C390.571 54.4838 387.865 55.6435 384.554 55.6435C377.931 55.6435 373.183 50.842 373.183 43.2807L373.185 43.2792Z" fill="black"/>
268        <path d="M404.094 55.09V27.7132H409.945V30.5287C411.711 28.4311 414.526 27.1613 418.004 27.1613C424.737 27.1613 429.54 31.9628 429.54 39.5256V55.09H423.413V40.0776C423.413 35.6063 420.985 32.9582 417.01 32.9582C413.035 32.9582 410.221 35.7179 410.221 40.0776V55.09H404.094Z" fill="black"/>
269        <path d="M198.402 29.1948C200.322 27.8779 202.588 27.1553 204.919 27.1156C212.667 27.1156 218.053 32.4069 218.053 40.248V55.1868H210.976V40.9135C210.976 36.5651 208.331 34.0138 204.742 34.0138C200.866 34.0138 198.503 36.7664 198.503 40.9135V55.1789H191.396V19.4648L198.402 15.4V29.1948Z" fill="black"/>
270        <path d="M103.932 38.1963C104.408 38.3098 104.907 38.4286 105.423 38.558C110.62 39.8796 114.59 41.6751 114.592 46.8729C114.592 52.2592 109.3 55.9421 102.684 55.9421C97.2046 55.9341 93.0464 54.4255 90.4 50.0771L96.6389 46.4878C97.8654 48.9456 99.9429 50.1738 102.873 50.1706C105.624 50.1706 107.315 48.9441 107.315 47.147C107.315 45.35 104.955 44.7874 100.887 43.9349C95.3125 42.7051 91.7232 40.4406 91.7232 35.7151C91.7232 30.6044 96.9194 27.1134 102.683 27.1134C107.597 27.1134 110.996 28.4382 114.02 32.224L108.068 35.6279C106.936 33.6629 105.233 32.6994 102.776 32.6994C100.225 32.6994 98.8099 33.8325 98.8099 35.3458C98.8099 36.9754 100.923 37.4792 103.932 38.1963Z" fill="black"/>
271        <path d="M118.368 55.1869L125.361 55.179V27.1157L118.368 31.2074V55.1869Z" fill="black"/>
272        <path d="M143.597 27.1157C135.848 27.1157 130.275 32.8824 130.275 40.443V55.1885L137.371 55.1806V40.9152C137.371 36.7681 139.734 34.0155 143.61 34.0155C147.199 34.0155 149.844 36.5668 149.844 40.9152V55.1806H156.921V40.443C156.921 33.2596 151.461 27.1157 143.597 27.1157Z" fill="black"/>
273        <path d="M167.86 41.4794C167.86 45.8278 170.789 49.1335 175.137 49.1335C177.972 49.1335 180.43 47.71
27352 181.561 45.354L187.607 48.853C186.361 51.0287 184.558 52.8321 182.382 54.0761C180.207 55.3201 177.739 55.9603 175.232 55.9286C166.843 55.9365 160.688 49.8894 160.688 41.481C160.688 33.0726 166.734 27.1173 175.232 27.1173C180.522 27.1173 185.244 29.8684 187.512 34.1106L181.466 37.7031C180.872 36.5415 179.964 35.5717 178.845 34.9029C177.725 34.2342 176.44 33.8935 175.137 33.9204C170.979 33.9204 167.86 37.0375 167.86 41.4794Z" fill="black"/>
274        <path d="M0 15.3998C0 7.44751 6.44769 0.999817 14.4 0.999817H57.6C65.5523 0.999817 72 7.44751 72 15.3998V58.5998C72 66.5521 65.5523 72.9998 57.6 72.9998H14.4C6.44769 72.9998 0 66.5521 0 58.5998V15.3998Z" fill="#F04126"/>
275        <path d="M62.082 32.4584C60.84 30.6929 59.1106 29.3691 57.0841 28.6349C55.7808 28.1475 54.4146 27.907 53.0232 27.9211C50.6005 27.9369 48.0316 28.6962 45.5995 30.119L23.9808 42.7749C20.6164 44.7432 18.1151 44.4791 16.6011 43.9068C15.4896 43.5106 14.5448 42.7921 13.864 41.8316C13.1817 40.8694 12.817 39.739 12.8123 38.5615C12.806 37.3824 13.155 36.2473 13.8263 35.2788C14.496 34.3088 15.4346 33.5809 16.5477 33.1674C18.0475 32.581 20.5441 32.287 23.9305 34.2129L26.3658 35.6058C26.2243 36.0948 26.119 36.5979 26.0529 37.101L25.9272 38.0537L44.3701 27.4071L44.4078 27.3835C44.9015 27.0345 45.2426 26.5156 45.37 25.9261C45.4973 25.335 45.3999 24.7218 45.0933 24.2014C44.7883 23.681 44.3009 23.2959 43.7208 23.1182C43.1422 22.9406 42.5244 22.9862 41.9788 23.2471L41.9616 23.255L35.9921 26.7216L29.911 23.244L29.8764 23.2267C29.3246 22.9689 28.702 22.9296 28.1235 23.1151C27.5449 23.3006 27.0607 23.6936 26.7604 24.2234C26.4601 24.7533 26.3721 25.3711 26.5136 25.9622C26.6535 26.5534 27.0088 27.0675 27.5135 27.4071L31.2237 29.509L30.7144 29.8093L30.7034 29.8156C29.9817 30.2731 29.3214 30.8202 28.7366 31.4412L26.3013 30.0373C22.3757 27.8017 18.2865 27.3222 14.79 28.6884C13.3326 29.2512 11.9916 30.1457 10.9131 31.2777C9.83459 32.4081 9.00292 33.79 8.50927 35.2773C7.67288 37.8839 7.87097 40.6745 9.0721 43.1349C10.2732 45.5953 12.35 47.4709 14.9174 48.4142C16.2223 48.9015 17.5916 49.1421 18.9798 49.1279C21.4009 49.1122 23.9683 48.3513 26.4035 46.9269L26.4413 46.9033L48.0112 34.2726C51.374 32.3059 53.8769 32.57 55.394 33.1407C56.504 33.5353 57.4504 34.2506 58.1327 35.2112C58.8135 36.1734 59.1735 37.3053 59.1766 38.4844C59.1782 39.1054 59.0839 39.7217 58.8984 40.305C58.6248 41.1131 58.1689 41.8661 57.5762 42.4793C56.9851 43.094 56.2509 43.5798 55.4506 43.8848C53.9508 44.4696 51.4511 44.7621 48.0647 42.8377L45.6294 41.4448C45.785 40.9103 45.8951 40.3632 45.9611 39.8129L46.0743 38.8681L31.7095 47.2791V52.8382L41.284 47.2476L41.2997 47.2382C42.0182 46.7807 42.6785 46.2336 43.2617 45.6126L45.6986 47.0165C49.6399 49.2443 53.7291 49.7222 57.2052 48.3654C59.2269 47.6061 60.939 46.2635 62.1606 44.4822C63.3806 42.7025 64.0157 40.6194 63.9969 38.4624H64C63.9874 36.3007 63.3271 34.2255 62.0851 32.46L62.082 32.4584Z" fill="white"/>
276        </g>
277        <defs>
278        <clipPath id="clip0_2232_6547">
279        <rect width="430" height="80" fill="white"/>
280        </clipPath>
281        </defs>
282      </svg>
283      
284
285<script type="module">
286  let currentStepSelector = "#usernameForm";
287
288const loginForm = document.getElementById("submitForm");
289const spinner = document.getElementById("spinner");
290
291// Form submit will happen natively on 'enter' press
292// but Nectary buttons don't trigger submit so they need some help
293const nextButton = document.getElementById("next");
294nextButton.addEventListener("-click", () => {
295  loginForm.requestSubmit();
296});
297
298const prevButton = document.getElementById("previous");
299/**
300 * Go to previous step
301 */
302prevButton.addEventListener("-click", (e) => {
303  e.preventDefault();
304  document.getElementById("loginError").classList.add("hidden");
305  goToFormStep("#usernameForm");
306});
307
308// reload page if browser back button is hit
309// ex. sinch id email -> redirected to sinch login -> user hits browser back button
310window.onpageshow = function (event) {
311  if (event.persisted) {
312    window.location.reload();
313  }
314};
315
316/**
317 * Hijack submit
318 */
319loginForm.addEventListener("submit", (e) => {
320  e.preventDefault();
321
322  const buttonText = nextButton.text;
323  if (buttonText !== "Next" && buttonText !== "Log in") {
324    return false;
325  }
326
327  nextButton.text = "";
328  document.body.classList.remove("error");
329
330  const nextMethod = getStepElement(currentStepSelector).data("next");
331  console.info(`Calling ${nextMethod}`);
332  // form methods will complete any network requests and dom manipulation for next step
333  formMethods[nextMethod]();
334});
335
336window.onpopstate = function () {
337  if (currentStepSelector !== "#usernameForm") {
338    return location.reload();
339  }
340  history.back();
341};
342
343getStepElement(currentStepSelector, "input").first().focus();
344
345history.pushState({}, "", "");
346
347// Display an error message if exists
348const error_message = "False";
349if (error_message !== "False") {
350  showMessage(error_message, "error");
351}
352
353/**
354 * Returns a user-friendly error message based on the error object's status code.
355 *
356 * @param {Object} error - The error object.
357 * @param {number} error.status - The HTTP status code representing the error.
358 * @param {string} [error.message] - The optional error message.
359 * @returns {string} A user-friendly error message corresponding to the error status.
360 */
361function getErrorMessage(error) {
362  switch (error.status) {
363    case 403:
364    case 400:
365      return error.message || "Bad username or password";
366    case 503:
367      return "Login temporarily disabled";
368    default:
369      return "Login service is down";
370  }
371}
372
373/**
374 * Check username and go to password form
375 */
376async function checkUsername() {
377  const email = getStepElement(currentStepSelector, "input")
378    .first()
379    .value.toLowerCase();
380  // @TODO: better email validation
381  if (!email) {
382    spinner.classList.remove("hidden");
383    nextButton.text = "Next";
384    showMessage("Please enter a valid email", "error");
385  } else {
386    const data = new FormData();
387    data.append("email", email);
388    try {
389      await post("/login/email", data);
390      goToFormStep("#passwordForm");
391    } catch (e) {
392      Sentry.captureException(e);
393      showMessage(getErrorMessage(e), "error");
394      spinner.classList.add("hidden");
395      nextButton.text = "Next";
396    }
397  }
398}
399
400/**
401 * Get otp secret from url param string
402 * @param {string} otp
403 * @return {false|string}
404 */
405function getSecretFromOTP(otp) {
406  const secretStart = otp.indexOf("secret=");
407  const secretEnd = otp.indexOf("&", secretStart);
408
409  if (secretStart > -1 && secretEnd > -1) {
410    return otp.substring(secretStart + 7, secretEnd);
411  }
412  return false;
413}
414
415/**
416 * Check password and go to either auth challenge, tfa auth, or tfa setup forms
417 */
418async function checkPassword() {
419  const data = new FormData();
420  data.append(
421    "password",
422    getStepElement(currentStepSelector, "#password").first().value,
423  );
424  try {
425    const response = await post("/login/password", data);
426    if (response.step === "challenge") {
427      goToFormStep("#challengeForm");
428      showMessage(
429        "We’ve detected unusual login activity. For security reasons, we’ve sent you a code to your email to validate your account.",
430        "success",
431      );
432    } else if (response.otp) {
433      const qr = new QRious({
434        size: 128,
435        value: response.otp,
436      });
437      const qrURL = qr.toDataURL();
438      const otpSecret = getSecretFromOTP(response.otp);
439
440      const img = document.createElement("img");
441      img.src = qrURL;
442      document.getElementById("otp-qr").appendChild(img);
443      document.getElementById("otp-secret").textContent = otpSecret;
444      document.getElementById("recovery-code").textContent = response.secret;
445      goToFormStep("#tfaSetupForm");
446
447      // manually handle stepper login button
448      nextButton.classList.add("hidden");
449      const tfaLoginButton = document.getElementById("tfa-step-login");
450      tfaLoginButton.addEventListener("-click", () => {
451        tfaLoginButton.text = "";
452        document
453          .getElementById("tfa-step-login-spinner")
454          .classList.remove("hidden");
455        // need to explicitly call complete2fa instead of requesting form submission
456        /* loginForm.requestSubmit(); */
457        complete2fa();
458      });
459    } else {
460      goToFormStep("#tfaForm");
461    }
462  } catch (e) {
463    Sentry.captureException(e);
464    showMessage(getErrorMessage(e), "error");
465    spinner.classList.add("hidden");
466    nextButton.text = "Log in";
467  }
468}
469
470/**
471 * Check 2fa auth code
472 */
473async function check2fa() {
474  const data = new FormData();
475
476  const rememberMeCheckbox = document.getElementById("otp-checkbox");
477  if (rememberMeCheckbox.getAttribute("checked")) {
478    data.set("otp-remember-me", true);
479  }
480
481  const otpInput = getStepElement(currentStepSelector, "input").nodes;
482  data.set("otp", otpInput[0].value);
483  if (otpInput.length > 1 && otpInput[1].checked) {
484    data.set(otpInput[1].name, otpInput[1].checked);
485  }
486
487  try {
488    await post("/login/2fa", data);
489    showMessage("Great, we're logging you in.", "success");
490  } catch (e) {
491    Sentry.captureException(e);
492    showMessage(getErrorMessage(e), "error");
493    spinner.classList.add("hidden");
494    nextButton.text = "Next";
495  }
496}
497
498/**
499 * Complete 2fa auth setup
500 */
501async function complete2fa() {
502  const data = new FormData();
503
504  const rememberMeCheckbox = document.getElementById("otp-setup-checkbox");
505  if (rememberMeCheckbox.getAttribute("checked")) {
506    data.set("otp-remember-me", true);
507  } else {
508    data.set("otp-remember-me", false);
509  }
510
511  const otpInput = getStepElement(currentStepSelector, "input").nodes;
512  data.set("otp", otpInput[0].value);
513  if (otpInput.length > 1 && otpInput[1].checked) {
514    data.set(otpInput[1].name, otpInput[1].checked);
515  }
516
517  try {
518    await post("/login/finish2fa", data);
519  } catch (e) {
520    Sentry.captureException(e);
521    showMessage(getErrorMessage(e), "error", "loginErrorComplete2fa");
522    document.getElementById("tfa-step-login-spinner").classList.add("hidden");
523    document.getElementById("tfa-step-login").text = "Log In";
524  }
525}
526
527// Each form's action is set dynamically, so a lookup is used
528const formMethods = {
529  checkUsername,
530  checkPassword,
531  check2fa,
532  complete2fa,
533};
534
535/**
536 * Handle form traversal
537 * Hides current form and shows next form
538 *
539 * @param {string} nextStepSelector - the next form to show
540 */
541function goToFormStep(nextStepSelector) {
542  spinner.classList.add("hidden");
543  hideMessage();
544
545  let currentStep = getStepElement(currentStepSelector);
546  if (nextStepSelector === "#passwordForm") {
547    prevButton.classList.remove("hidden");
548    const input = document.getElementById("password");
549    input.setAttribute("required", true);
550  } else {
551    prevButton.classList.add("hidden");
552  }
553
554  if (currentStepSelector === "#usernameForm") {
555    // ensure password form is not required in case we are coming back to username form
556    const passwordInput = document.getElementById("password");
557    passwordInput.removeAttribute("required");
558    currentStep.addClass("hidden");
559    currentStepSelector = nextStepSelector;
560    let nextStep = getStepElement(nextStepSelector);
561    const email = getStepElement("#usernameForm", "input")
562      .first()
563      .value.toLowerCase();
564    getStepElement(nextStepSelector, "input").first().value = email;
565    nextStep.removeClass("hidden");
566
567    if (getStepElement(currentStepSelector, "input").first()) {
568      getStepElement(currentStepSelector, "input").first().focus();
569    }
570
571    nextButton.text = "Log in";
572  } else {
573    currentStep.addClass("hidden");
574
575    currentStepSelector = nextStepSelector;
576    let nextStep = getStepElement(nextStepSelector);
577    nextStep.removeClass("hidden");
578
579    if (getStepElement(currentStepSelector, "input").first()) {
580      getStepElement(currentStepSelector, "input").first().focus();
581    }
582
583    nextButton.text = "Next";
584  }
585}
586
587/**
588 * Retrieves the first matching element based on a combined CSS selector.
589 *
590 * This function constructs a CSS selector by concatenating the `current` selector
591 * with an optional `selector` parameter, then queries the document for the first matching element.
592 *
593 * @param {string} current - The base CSS selector.
594 * @param {string} [selector=''] - An optional CSS selector to append to the current selector.
595 * @returns {Element|null} The first matching element or null if no match is found.
596 */
597function getStepElement(current, selector = "") {
598  const step = selector ? `${current} ${selector}` : current;
599  return u(step);
600}
601
602/**
603 * Does a post request
604 *
605 * @param {string} url - endpoint to request
606 * @param {FormData} data - data to send to endpoint
607 */
608function post(url, data) {
609  nextButton.text = "";
610  spinner.classList.remove("hidden");
611
612  data.append("csrf_token", "IjdkOWRlZjMyY2Q2ZjllZDk4YjgzNjgwOGMzMjg5ZGExY2I5ZWFlYjAi.arTILw.AhTKPwRiSvp20K8Tc6DwLRtTsGY");
613
614  return new Promise(function (resolve, reject) {
615    fetch(url, {
616      method: "Post",
617      body: data,
618    })
619      .then(function (response) {
620        response
621          .json()
622          .then(function (jsonResponse) {
623            if (response.ok) {
624              // May be auth redirect or Sinch migration
625              if (jsonResponse.redirect) {
626                return (window.location = jsonResponse.redirect);
627              }
628              resolve(jsonResponse);
629            } else {
630              reject({
631                message: jsonResponse.message,
632                status: response.status,
633                statusText: response.statusText,
634              });
635            }
636          })
637          .catch(function (err) {
638            Sentry.captureException(err);
639            reject({
640              status: response.status,
641              statusText: response.statusText,
642            });
643          });
644      })
645      .catch(function (err) {
646        Sentry.captureException(err);
647        reject({
648          status: 500,
649          statusText: err.message,
650        });
651      });
652  });
653}
654</script>
654
655
656<div id="mainContent">
657
658  <h1>Log in to Mailgun</h1>
659
660  
661
662  <form id="submitForm" class="login-form">
663    <!-- EMAIL/USERNAME FORM -->
664    <div id="usernameForm" data-next="checkUsername">
665      <p>Enter your email to continue</p>
666
667      <!-- Email input -->
668      <div class="labeled-input">
669        <input autocomplete="username" autofocus id="username" name="username" placeholder="Email Address" required
670          type="email" value="" />
671        <label for="username">Email Address</label>
672      </div>
673    </div>
674
675    <!-- PASSWORD FORM -->
676    <div class="hidden" data-next="checkPassword" id="passwordForm">
677      <p>Enter your password to continue</p>
678      <!-- Email input - Disabled -->
679      <input autocomplete="username" disabled id="username" placeholder="Email Address" type="email" />
680
681      <!-- Password input -->
682      <div class="labeled-input">
683        <input autocomplete="password" autofocus id="password" name="password" type="password" placeholder="password"
684          value="" />
685        <label for="password">Password</label>
686      </div>
687
688      <!-- "Forgot password" link -->
689      <sinch-link aria-label="Link" href="/recovery/new" standalone text="Forgot password?"></sinch-link>
690    </div>
691
692    <!-- TFA FORM -->
693    <div id='tfaForm' class='hidden' data-next='check2fa'>
694      <p>Enter your two-factor code to continue</p>
695      <!-- Two Factor Auth -->
696      <div class="labeled-input">
697        <input autocomplete="one-time-code" inputmode="numeric" autofocus id="tfa"
698          placeholder="Two-factor authentication token" type="number" />
699        <label for="tfa">
700          Two-factor authentication token
701        </label>
702      </div>
703
704      <!-- "Don't ask again" checkbox -->
705      <sinch-checkbox aria-label="Don't ask again" id="otp-checkbox"
706        text="Don't ask again on this computer."></sinch-checkbox>
707      
707<script>
708        <!-- because of course you have to manually manage state for a nectary checkbox 😑-->
709        manageCheckbox('otp-checkbox')
710      </script>
710
711      <br />
712      <br />
713      <sinch-link aria-label="2fa Recovery" class="small-text" href="/recovery/2fa"
714        text="Lost your authenticator?"></sinch-link>
715    </div>
716
717    <!-- TFA SET UP FORM -->
718    <div id='tfaSetupForm' class='hidden' data-next='complete2fa'>
719      
719<script type="module">
720        const stepper = document.getElementById('stepper');
721
722        document
723          .querySelectorAll('[id^="next-"]')
724          .forEach(button => button.addEventListener('-click', nextStep));
725
726        document
727          .querySelectorAll('[id^="prev-"]')
728          .forEach(button => button.addEventListener('-click', prevStep));
729
730        function nextStep() {
731          stepper.index = parseInt(stepper.index) + 1;
732        }
733
734        function prevStep() {
735          stepper.index = parseInt(stepper.index) - 1;
736        }
737      </script>
737
738
739      <h2 style="margin-bottom: 16px;">Set up Two-Factor Auth</h2>
740
741      <sinch-vertical-stepper aria-label="Stepper" id="stepper" index="1">
742        <!-- Install an authentication app -->
743        <sinch-vertical-stepper-item label="Step 1" description="Install an Authentication App">
744
745          <p>
746            You must have an authentication app installed to verify 2-factor auth
747            tokens.
748          </p>
749          <p>
750            We recommend one of the following:
751          </p>
752
753          <!-- Authentication Apps list-->
754          <ul class="breakout">
755            <li class="extra-margin">
756              2FAS &mdash;
757              <sinch-link aria-label="Link" class="small-text" external
758                href="https://play.google.com/store/apps/details?id=com.twofasapp" text="Android"></sinch-link>
759              <sinch-link aria-label="Link" class="small-text" external
760                href="https://apps.apple.com/us/app/2fas-auth/id1217793794" text="iOS"></sinch-link>
761            </li>
762
763            <li class="extra-margin">
764              Ravio OTP &mdash;
765              <sinch-link aria-label="Link" external
766                href="https://apps.apple.com/app/raivo-otp/id1459042137?platform=iphone" text="iOS"></sinch-link>
767            </li>
768
769            <li class="extra-margin">
770              Aegis &mdash;
771              <sinch-link aria-label="Link" external
772                href="https://play.google.com/store/apps/details?id=com.beemdevelopment.aegis"
773                text="Android"></sinch-link>
774            </li>
775
776            <li class="extra-margin">
777              Google Authenticator &mdash;
778              <sinch-link aria-label="Link" external
779                href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2"
780                text="Android"></sinch-link>
781              <sinch-link aria-label="Link" external
782                href="https://apps.apple.com/us/app/google-authenticator/id388497605" text="iOS"></sinch-link>
783            </li>
784
785            <li class="extra-margin">
786              Microsoft Authenticator &mdash;
787              <sinch-link aria-label="Link" class="small-text" external
788                href="https://play.google.com/store/apps/details?id=com.azure.authenticator"
789                text="Android"></sinch-link>
790              <sinch-link aria-label="Link" external
791                href="https://apps.apple.com/us/app/microsoft-authenticator/id983156458" text="iOS"></sinch-link>
792            </li>
793          </ul>
794
795          <div class="align-end">
796            <sinch-button aria-label="Next" id="next-1" text="Next" type="primary">
797            </sinch-button>
798          </div>
799        </sinch-vertical-stepper-item>
800
801        <!-- Input the auth code -->
802        <sinch-vertical-stepper-item label="Step 2" description="Scan QR Code">
803          <p>Use your authentication app to scan this QR code: </p>
804
805          <div id="otp-qr"></div>
806
807          <p>Or, manually type in the following secret: </p>
808
809          <div>
810            <pre id="otp-secret" class="code"></pre>
811            <div class="flex-fill">
812              <sinch-button id="copy-otp" type="secondary" text="Copy Code" aria-label="Copy Code">
813                <sinch-icon-file-copy slot="icon" />
814              </sinch-button>
815            </div>
816          </div>
817
818          <div class="align-end">
819            <sinch-button aria-label="Previous" id="prev-2" text="Previous" type="subtle-primary">
820            </sinch-button>
821            <sinch-button aria-label="Next" id="next-2" text="Next" type="primary">
822            </sinch-button>
823          </div>
824
825          
825<script>
826            // copy button
827            copyTextListener('copy-otp', 'otp-secret')
828          </script>
828
829        </sinch-vertical-stepper-item>
830
831        <!-- Save the recovery code -->
832        <sinch-vertical-stepper-item label="Step 3" description="Recovery Code">
833          <sinch-text>
834            Copy this code and keep it somewhere safe. You'll need it if you ever need to log in
835            without your device.
836          </sinch-text>
837          <div>
838            <pre id="recovery-code" class="code"></pre>
839            <div class="flex-fill">
840              <sinch-button id="copy-recovery" type="secondary" text="Copy Code" aria-label="Copy Code">
841                <sinch-icon-file-copy slot="icon" />
842              </sinch-button>
843            </div>
844            <br />
845            <sinch-checkbox id="recovery-checkbox" text='I have safely recorded this code' aria-label="Checkbox" />
846          </div>
847
848          <div class="align-end">
849            <sinch-button aria-label="Previous" id="prev-3" text="Previous" type="subtle-primary">
850            </sinch-button>
851            <sinch-button aria-label="Next" id="next-3" text="Next" type="primary">
852            </sinch-button>
853          </div>
854
855          
855<script>
856            // manage checkbox + next button
857            const nextBtn = document.getElementById('next-3')
858            nextBtn.setAttribute('disabled', true)
859            const checkbox = document.getElementById('recovery-checkbox')
860            checkbox.addEventListener('click', () => {
861              const checkedValue = !checkbox.getAttribute('checked')
862              checkbox.setAttribute('checked', checkedValue)
863              nextBtn.setAttribute('disabled', !checkedValue)
864            })
865
866            // copy button
867            copyTextListener('copy-recovery', 'recovery-code')
868          </script>
868
869        </sinch-vertical-stepper-item>
870
871        <!-- Confirm -->
872        <sinch-vertical-stepper-item label="Step 4" description="Confirm 2FA">
873
874          <!-- Two Factor Auth -->
875          <div class="labeled-input">
876            <input autocomplete="one-time-code" inputmode="numeric" autofocus id="otp"
877              placeholder="Enter your one-time code" type="number" />
878            <label for="otp">
879              Enter your one-time code
880            </label>
881          </div>
882
883          <!-- "Don't ask again" checkbox -->
884          <sinch-checkbox aria-label="Don't ask again" id="otp-setup-checkbox"
885            text="Don't ask again on this computer."></sinch-checkbox>
886          
886<script>
887            <!-- because of course you have to manually manage state for a nectary checkbox 😑-->
888            manageCheckbox("otp-setup-checkbox")
889          </script>
889
890
891          <div class="hidden" id="loginErrorComplete2fa" style="padding-top: 1em;"></div>
892          <div class="flex-fill">
893            <sinch-button aria-label="Previous" id="prev-4" size="l" text="Previous" type="subtle-primary">
894            </sinch-button>
895            <sinch-button aria-label="Log In" id="tfa-step-login" size="l" text="Log In" type="cta-primary">
896              <sinch-spinner class="hidden" id="tfa-step-login-spinner" slot="icon" />
897            </sinch-button>
898          </div>
899        </sinch-vertical-stepper-item>
900      </sinch-vertical-stepper>
901    </div>
902
903    <!-- AUTHENTICATION CHALLENGE FORM -->
904    <div id='challengeForm' class='hidden' data-next='check2fa'>
905      <h2>Authentication Code</h2>
906
907      <p>Check your email for the code</p>
908
909      <!-- Two Factor Auth -->
910      <input autocomplete="off" autofocus class="show-placeholder" id="otp-auth-input" name="otp" placeholder="123456"
911        type="text" />
912
913      <!-- TODO: When we enable this we will need make a small change to the challenge logic in users -->
914      <!--
915        <div class='rememberMe'>
916            <input type='checkbox' id='otp-checkbox-challenge' name="otp-remember-me" checked/>
917            <label for='otp-checkbox-challenge' class='labelGroup__label'>Don't ask again on this computer</label>
918        </div>
919      -->
920    </div>
921
922    <div class="hidden" id="loginError" style="padding-top: 1em;">
923    </div>
924
925    <div class='flex-fill'>
926      <sinch-button aria-label="Back" class="hidden" id="previous" text="Back" size="l" type="subtle-primary">
927      </sinch-button>
928
929      <sinch-button aria-label="Next" id="next" text="Next" size="l" type="cta-primary">
930        <sinch-spinner class="hidden" id="spinner" slot="icon" />
931      </sinch-button>
932    </div>
933
934    <!-- Submit - hidden input for keyboard support -->
935    <button hidden type="submit"></button>
936  </form>
937</div>
938
939    </section>
940  </main>
941
942  
942<script type="module">
943    if ('') {
944      showMessage('', '');
945    }
946  </script>
946
947
948  <footer>
949    
950    <div>
951  Don't have an account?
952  <a href="https://signup.mailgun.com/new/signup">Signup</a>
953</div>
954
955<div>
956  <ul>
957    <li>© <a href="https://sinch.com">Sinch</a></li>
958
959    <li>
960      <a href="https://www.mailgun.com/terms">
961        Terms of Service</a>
962    </li>
963
964    <li>
965      <a href="https://www.mailgun.com/privacy-policy"> Privacy
966        Policy</a>
967    </li>
968
969    <li><a href="https://www.mailgun.com/aup"> Sending Policy</a>
970    </li>
971  </ul>
972</div>
973  </footer>
974</body>
975
976</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.