[GH-ISSUE #434] CORS problem when using /api/view #279

Closed
opened 2026-03-15 13:37:03 +03:00 by kerem · 37 comments
Owner

Originally created by @baiomys on GitHub (Feb 1, 2025).
Original GitHub issue: https://github.com/axllent/mailpit/issues/434

HI.

Is it possible to preprocess HTML content to do JS analog of

const links = document.getElementById('doc').contentWindow.document.getElementsByTagName('a');
 for (let link of links) {
   link.setAttribute('target', '_blank'); 
 }

for external links ?

When showing HTML content in IFRAME external links fail if not preprocessed, but it's not an option when using cross-domain IFRAME. Prohibited by CORS.

Thanks.

Originally created by @baiomys on GitHub (Feb 1, 2025). Original GitHub issue: https://github.com/axllent/mailpit/issues/434 HI. Is it possible to preprocess HTML content to do JS analog of ``` const links = document.getElementById('doc').contentWindow.document.getElementsByTagName('a'); for (let link of links) { link.setAttribute('target', '_blank'); } ``` for external links ? When showing HTML content in IFRAME external links fail if not preprocessed, but it's not an option when using cross-domain IFRAME. Prohibited by CORS. Thanks.
kerem closed this issue 2026-03-15 13:37:08 +03:00
Author
Owner

@axllent commented on GitHub (Feb 2, 2025):

Hi. Did you set the the --api-cors "*" flag (MP_API_CORS="*" env) in Mailpit?

<!-- gh-comment-id:2629183293 --> @axllent commented on GitHub (Feb 2, 2025): Hi. Did you set the the `--api-cors "*"` flag (`MP_API_CORS="*"` env) in Mailpit?
Author
Owner

@axllent commented on GitHub (Feb 2, 2025):

Sorry, I just realised that the CORS policy only applies to the /api/* route, and not the /view/*.html route, so that won't currently help.

I prefer not to hardcode target="_blank" in the HTML version as that may impact other existing integrations and/or tests. I believe that if I fix the CORS to also include the /view/*.html route too, then your JavaScript should be allowed to run.

<!-- gh-comment-id:2629184880 --> @axllent commented on GitHub (Feb 2, 2025): Sorry, I just realised that the CORS policy only applies to the `/api/*` route, and not the `/view/*.html` route, so that won't currently help. I prefer not to hardcode `target="_blank"` in the HTML version as that may impact other existing integrations and/or tests. I believe that if I fix the CORS to also include the `/view/*.html` route too, then your JavaScript should be allowed to run.
Author
Owner

@baiomys commented on GitHub (Feb 2, 2025):

Thanks, currently Caddy routes all api calls to/from several mailpits and CORS problem can be solved by complicated redirects,
but it is kind of 'dirty hack' I prefer no to use.

<!-- gh-comment-id:2629186592 --> @baiomys commented on GitHub (Feb 2, 2025): Thanks, currently Caddy routes all api calls to/from several mailpits and CORS problem can be solved by complicated redirects, but it is kind of 'dirty hack' I prefer no to use.
Author
Owner

@axllent commented on GitHub (Feb 2, 2025):

Does that mean that it will work for you if I apply (fix) the CORS policy to the html route?

<!-- gh-comment-id:2629188239 --> @axllent commented on GitHub (Feb 2, 2025): Does that mean that it will work for you if I apply (fix) the CORS policy to the html route?
Author
Owner

@baiomys commented on GitHub (Feb 2, 2025):

IMHO fixing CORS issue will help to prevent further questions from other users.
You program is getting popular.
=)

<!-- gh-comment-id:2629189761 --> @baiomys commented on GitHub (Feb 2, 2025): IMHO fixing CORS issue will help to prevent further questions from other users. You program is getting popular. =)
Author
Owner

@baiomys commented on GitHub (Feb 2, 2025):

Alternatively you can use endpoint already covered by CORS

/api/v1/message/{ID}/part/{PartID}

using part id = html for example to return html

It will also reduce number of routes to handle on reverse-proxy side.

<!-- gh-comment-id:2629199629 --> @baiomys commented on GitHub (Feb 2, 2025): Alternatively you can use endpoint already covered by CORS /api/v1/message/{ID}/part/{PartID} using part id = html for example to return html It will also reduce number of routes to handle on reverse-proxy side.
Author
Owner

@axllent commented on GitHub (Feb 2, 2025):

I have pushed a change for this to the axllent/mailpit:edge docker image (I haven't released it yet). Would you be able to test that to see if it solves your issue please? Thanks.

<!-- gh-comment-id:2629220345 --> @axllent commented on GitHub (Feb 2, 2025): I have pushed a change for this to the `axllent/mailpit:edge` docker image (I haven't released it yet). Would you be able to test that to see if it solves your issue please? Thanks.
Author
Owner

@baiomys commented on GitHub (Feb 2, 2025):

No luck
Uncaught SecurityError: Failed to read a named property 'document' from 'Window': Blocked from accessing a cross-origin frame.
It seems that Access-Control-Allow-Origin does not affect IFRAME access as expected
https://stackoverflow.com/questions/23362842/access-control-allow-origin-not-working-for-iframe-within-the-same-domain

Also tried to manually inject Access-Control-Allow-Origin in caddy response. Same result.

Thanks for trying.

<!-- gh-comment-id:2629230928 --> @baiomys commented on GitHub (Feb 2, 2025): No luck ` Uncaught SecurityError: Failed to read a named property 'document' from 'Window': Blocked from accessing a cross-origin frame. ` It seems that Access-Control-Allow-Origin does not affect IFRAME access as expected https://stackoverflow.com/questions/23362842/access-control-allow-origin-not-working-for-iframe-within-the-same-domain Also tried to manually inject Access-Control-Allow-Origin in caddy response. Same result. Thanks for trying.
Author
Owner

@axllent commented on GitHub (Feb 2, 2025):

Hmmm, that is unfortunate. OK, what do you think would solve this issue for you? Is it just the target="_blank", or is there more to it? You are able to load the iframe from your application, however the links currently just open within the iframe, am I correct?

<!-- gh-comment-id:2629238316 --> @axllent commented on GitHub (Feb 2, 2025): Hmmm, that is unfortunate. OK, what do you think would solve this issue for you? Is it just the `target="_blank"`, or is there more to it? You are able to load the iframe from your application, however the links currently just open within the iframe, am I correct?
Author
Owner

@baiomys commented on GitHub (Feb 2, 2025):

IMHO hardcoding target="_blank" can be helpful not only in my case.
And I am not asking to do something exceptional just for me and right away.

This IFRAME dilemma is obvious and can arise any time soon.

To preserve current API structure, solution can be bound to existing /view or /api/v1/message/{ID}/part/{PartID} endpoint using new context.

<!-- gh-comment-id:2629244702 --> @baiomys commented on GitHub (Feb 2, 2025): IMHO hardcoding target="_blank" can be helpful not only in my case. And I am not asking to do something exceptional just for me and right away. **This IFRAME dilemma is obvious and can arise any time soon.** To preserve current API structure, solution can be bound to existing /view or /api/v1/message/{ID}/part/{PartID} endpoint using new context.
Author
Owner

@axllent commented on GitHub (Feb 2, 2025):

I've done a bit more reading, and it is a browser-based security restriction to prevent hacking (you won;t be able to work around it via JavaScript). For instance, if I embedded the Gmail login page within an iframe on my malicious site, and then used JavaScript to listen on the input, and submit the data elsewhere as you log in.

  1. So I do not believe JavaScript is an option here if you are running the frontend and Mailpit API on different domains.
  2. I am still reluctant to modify the default "preview HTML" as-is because of backwards compatibility.

If I was to manipulate the HTML on the Mailpit end, I'm not entirely sure what the best way is. The reason is that while there are DOM-parsing libraries there, they always manipulate more than just the anchor tags, they also create <head> & <body> tags (if they don't already exist), and potentially "fix" broken HTML. This may not be bad in your case, but it would definitely affect user testing.

So, I could potentially add a URL variable option, so something like /view/<id>.html?embed=1 or something, which then could do the manipulation, and we don't care if it adds or modifies HTML code. There are some big risks though with embedding as an iframe which you should be aware of, for instance if the HTML email contains inline JavaScript, but that is a different discussion.

The other option is that your application reads the HTML message data from the AP, and then injects it into your page. I do something similar already in Mailpit itself, injecting the data into a blank iframe using srcdoc instead of src. This gives you far more control, however does require several HTML manipulation processes (converting embedded image paths, and I also sanitize the HTML to remove javascript etc). Most of this could be copied from Mailpit's code (although it is in Vue). This is the best way I think, but requires more work on your end. It does however allow you more control to change things like the iframe's height, and opening links in new windows etc.

<!-- gh-comment-id:2629247547 --> @axllent commented on GitHub (Feb 2, 2025): I've done a bit more reading, and it is a browser-based security restriction to prevent hacking (you won;t be able to work around it via JavaScript). For instance, if I embedded the Gmail login page within an iframe on my malicious site, and then used JavaScript to listen on the input, and submit the data elsewhere as you log in. 1. So I do not believe JavaScript is an option here if you are running the frontend and Mailpit API on different domains. 2. I am still reluctant to modify the default "preview HTML" as-is because of backwards compatibility. If I was to manipulate the HTML on the Mailpit end, I'm not entirely sure what the best way is. The reason is that while there are DOM-parsing libraries there, they always manipulate more than just the anchor tags, they also create `<head>` & `<body>` tags (if they don't already exist), and potentially "fix" broken HTML. This may not be bad in your case, but it would definitely affect user testing. So, I could potentially add a URL variable option, so something like `/view/<id>.html?embed=1` or something, which then could do the manipulation, and we don't care if it adds or modifies HTML code. There are some big risks though with embedding as an iframe which you should be aware of, for instance if the HTML email contains inline JavaScript, but that is a different discussion. The other option is that your application reads the HTML message data from the AP, and then injects it into your page. I do something similar already in Mailpit itself, injecting the data into a blank iframe using `srcdoc` instead of `src`. This gives you far more control, however does require several HTML manipulation processes (converting embedded image paths, and I also sanitize the HTML to remove javascript etc). Most of this could be copied from Mailpit's code (although it is in Vue). This is the best way I think, but requires more work on your end. It does however allow you more control to change things like the iframe's height, and opening links in new windows etc.
Author
Owner

@baiomys commented on GitHub (Feb 2, 2025):

mailpit instance is always self hosted, so you can relatively easy attach regex/sed query via api request parameter and DON'T CARE about result. End user should care.

srcdoc is not an option. It can be implemented only using Caddy templates, which capabilities are too limited.
I cannot afford to process all requests on single core point without distributing among pits.

<!-- gh-comment-id:2629248515 --> @baiomys commented on GitHub (Feb 2, 2025): mailpit instance is always self hosted, so you can relatively easy attach regex/sed query via api request parameter and DON'T CARE about result. End user should care. srcdoc is not an option. It can be implemented only using Caddy templates, which capabilities are too limited. I cannot afford to process all requests on single core point without distributing among pits.
Author
Owner

@axllent commented on GitHub (Feb 2, 2025):

You are embedding the html in an iframe, right? How are you dealing with things like iframe height and styling?

Just so I understand 100% - is adding target="_blank" the only change you need here to allow your application to preview the HTML message?

<!-- gh-comment-id:2629261396 --> @axllent commented on GitHub (Feb 2, 2025): You are embedding the html in an iframe, right? How are you dealing with things like iframe height and styling? Just so I understand 100% - is adding `target="_blank"` the only change you need here to allow your application to preview the HTML message?
Author
Owner

@baiomys commented on GitHub (Feb 2, 2025):

Well, it's definitely not a secret.
Currently I am using Jinja template.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
// cookie with JWT containing ONLY expiration timestamp
document.cookie = "usess={{ jwt }}; domain={{ domain }}; path=/";

function unhide() {
  document.getElementById('back').style.background = 'none';
  document.getElementById('glass').style.display = 'block';
}

function show() {
  unhide();
  const links = document.getElementById('glass').contentWindow.document.getElementsByTagName('a');
  for (let link of links) {
    link.setAttribute('target', '_blank'); 
  }
}

setTimeout(() => { unhide() }, 2000);
</script>

<style>
        body { background: radial-gradient(#eee 50%, #999) fixed  !important;  }

        @media only screen and (max-width: 800px) {
            .somepad { padding: 0%; }
            .cntr { display: none; }
            .screen:before { height: 96vh }
        }

        @media only screen and (min-width: 800px) {
            .somepad { padding: 4%;  }
            .cntr { display:inline-block; }
            .screen:before {
            padding-top: 140%;
            }
        }

        .somepad {  margin: 0 auto; max-width: 1024px;  }

        .screen {
            background: radial-gradient(#000c04 70%, #c6c6c6);
            box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 0 2px rgba(0, 0, 0, 0.4) inset;
            border-radius: 6% / 4%;
            overflow: hidden;
            position: relative;
            width: 100%;
        }

        .screen:before {
            content: "";
            display: block;
            background: #F5F6F8;
            border-radius: 5%/3.5%;
            margin: .7%;
        }

        .screen:after {
            background: #2c3449;
            border-radius: 50%;
            box-shadow: 0 1px 1px 0 #fff, 0 1px 1px #000 inset;
            content: "";
            position: absolute;
            top: 4.5%;
            left: 49.2%;
            padding-top: 1.6%;
            width: 1.6%;
        }

        .cntr {
           position: absolute;
           top: 4.5%;
           left: 6%;
           width: 6.4%;
           color:rgb(0, 0, 0);
           text-align: left;
        }

        .button {
            /*background: #fff;*/
            background: #555;
            border-radius: 50%;
            bottom: 2.6%;
            box-shadow: 0 0 1px 0 #fff, 0 8px 7px rgba(0, 0, 0, 0.08) inset;
            height: 0;
            left: 47%;
            padding-top: 6.4%;
            position: absolute;
            width: 6.4%;
        }

        .button:after {
            background: #555;
            border-radius: 25%;
            box-shadow: 1px 1px 0 0 rgba(255, 255, 255, 0.7) inset, -1px -1px 0 0 rgba(255, 255, 255, 0.5) inset, 0 2px 5px rgba(0, 0, 0, 0.1) inset, -1px 0 8px rgba(0, 0, 0, 0.2), 1px 1px 1px rgba(0, 0, 0, 0.1);
            content: "";
            display: block;
            left: 35%;
            padding-top: 30%;
            position: absolute;
            top: 38%;
            width: 30%;
        }

        .viewport {
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            top: 0;
            margin: 13% 6%;
            border: 1px solid rgba(0, 0, 0, .2);
            background:url("https://cdnjs.cloudflare.com/ajax/libs/galleriffic/2.0.1/css/loader.gif") center no-repeat;
        }

        .content {
            height: 100%;
            width: 100%;
            border: none;
            display:none;
            background: #fff;
        }
    </style>
</head>

<body>
    <div class="somepad">
        <div class="screen">
            <span class="cntr">{{ hits }}</span>
            <div class="viewport" id="back">
                <iframe class="content" id="glass" src="{{ url }}" onload="show()"></iframe>
            </div>
            <div id="bada-boom" class="button"></div>
        </div>
    </div>
</body>
</html>
<!-- gh-comment-id:2629262595 --> @baiomys commented on GitHub (Feb 2, 2025): Well, it's definitely not a secret. Currently I am using Jinja template. ``` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script> // cookie with JWT containing ONLY expiration timestamp document.cookie = "usess={{ jwt }}; domain={{ domain }}; path=/"; function unhide() { document.getElementById('back').style.background = 'none'; document.getElementById('glass').style.display = 'block'; } function show() { unhide(); const links = document.getElementById('glass').contentWindow.document.getElementsByTagName('a'); for (let link of links) { link.setAttribute('target', '_blank'); } } setTimeout(() => { unhide() }, 2000); </script> <style> body { background: radial-gradient(#eee 50%, #999) fixed !important; } @media only screen and (max-width: 800px) { .somepad { padding: 0%; } .cntr { display: none; } .screen:before { height: 96vh } } @media only screen and (min-width: 800px) { .somepad { padding: 4%; } .cntr { display:inline-block; } .screen:before { padding-top: 140%; } } .somepad { margin: 0 auto; max-width: 1024px; } .screen { background: radial-gradient(#000c04 70%, #c6c6c6); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 0 2px rgba(0, 0, 0, 0.4) inset; border-radius: 6% / 4%; overflow: hidden; position: relative; width: 100%; } .screen:before { content: ""; display: block; background: #F5F6F8; border-radius: 5%/3.5%; margin: .7%; } .screen:after { background: #2c3449; border-radius: 50%; box-shadow: 0 1px 1px 0 #fff, 0 1px 1px #000 inset; content: ""; position: absolute; top: 4.5%; left: 49.2%; padding-top: 1.6%; width: 1.6%; } .cntr { position: absolute; top: 4.5%; left: 6%; width: 6.4%; color:rgb(0, 0, 0); text-align: left; } .button { /*background: #fff;*/ background: #555; border-radius: 50%; bottom: 2.6%; box-shadow: 0 0 1px 0 #fff, 0 8px 7px rgba(0, 0, 0, 0.08) inset; height: 0; left: 47%; padding-top: 6.4%; position: absolute; width: 6.4%; } .button:after { background: #555; border-radius: 25%; box-shadow: 1px 1px 0 0 rgba(255, 255, 255, 0.7) inset, -1px -1px 0 0 rgba(255, 255, 255, 0.5) inset, 0 2px 5px rgba(0, 0, 0, 0.1) inset, -1px 0 8px rgba(0, 0, 0, 0.2), 1px 1px 1px rgba(0, 0, 0, 0.1); content: ""; display: block; left: 35%; padding-top: 30%; position: absolute; top: 38%; width: 30%; } .viewport { position: absolute; bottom: 0; left: 0; right: 0; top: 0; margin: 13% 6%; border: 1px solid rgba(0, 0, 0, .2); background:url("https://cdnjs.cloudflare.com/ajax/libs/galleriffic/2.0.1/css/loader.gif") center no-repeat; } .content { height: 100%; width: 100%; border: none; display:none; background: #fff; } </style> </head> <body> <div class="somepad"> <div class="screen"> <span class="cntr">{{ hits }}</span> <div class="viewport" id="back"> <iframe class="content" id="glass" src="{{ url }}" onload="show()"></iframe> </div> <div id="bada-boom" class="button"></div> </div> </div> </body> </html> ```
Author
Owner

@axllent commented on GitHub (Feb 2, 2025):

Thanks, but what about my second question of my last message?

<!-- gh-comment-id:2629266987 --> @axllent commented on GitHub (Feb 2, 2025): Thanks, but what about my second question of my last message?
Author
Owner

@baiomys commented on GitHub (Feb 2, 2025):

All is working in pre-alpha stage.

https://t.me/org_mailbot

Feel free to test if you use telegram.

Currently there is only one testing pit, so CORS is not a problem for now, but there will be more soon.
So if you kindly agree to fix this nasty issue it'll be great.

<!-- gh-comment-id:2629269791 --> @baiomys commented on GitHub (Feb 2, 2025): All is working in pre-alpha stage. https://t.me/org_mailbot Feel free to test if you use telegram. Currently there is only one testing pit, so CORS is not a problem for now, but there will be more soon. So if you kindly agree to fix this nasty issue it'll be great.
Author
Owner

@axllent commented on GitHub (Feb 2, 2025):

No sorry, I don't have Telegram. Ok, let me think about this a little bit more. There is actually a way an embedded page can call a function (if it exists) in a parent page, which may be the better solution. Sure, you need just the target set, however others may require other functionality too. I've actually done this before in another project, so I'll need to do a bit of digging to see how that worked, and what the limitations (if any) were. At this stage I'm most concerned about security, and also flexibility.

<!-- gh-comment-id:2629286329 --> @axllent commented on GitHub (Feb 2, 2025): No sorry, I don't have Telegram. Ok, let me think about this a little bit more. There is actually a way an embedded page can call a function (if it exists) in a parent page, which may be the better solution. Sure, you need just the target set, however others may require other functionality too. I've actually done this before in another project, so I'll need to do a bit of digging to see how that worked, and what the limitations (if any) were. At this stage I'm most concerned about security, and also flexibility.
Author
Owner

@baiomys commented on GitHub (Feb 2, 2025):

Glad to hear that you are planning major improvements in project.

The most obvious way is to encode

<!-- gh-comment-id:2629287410 --> @baiomys commented on GitHub (Feb 2, 2025): Glad to hear that you are planning major improvements in project. The most obvious way is to encode <script> section containing payload using base64, then pass it to mailpit in env variable and inject in HTML on api call to /view. Sure some examples will be handy. It can be used directly `src="data:text/javascript;base64,Y29uc29sZSgnSGVsbG8sIFdvcmxkIScpOw=="`
Author
Owner

@baiomys commented on GitHub (Feb 2, 2025):

AI generated example using messages
Not sure it's 100% correct, but concept looks usable.

<!DOCTYPE html>
<head>
    <title>Parent Window</title>
    <script>
        function sendCode() {
            const iframe = document.getElementById('myIframe');
            const code = "console.log('Hello from the iframe!');"; // Code to send
            iframe.contentWindow.postMessage(code, '*'); // Send code to iframe
        }
    </script>
</head>
<body>
    <h1>Parent Window</h1>
    <button onclick="sendCode()">Send Code to Iframe</button>
    <iframe id="myIframe" src="iframe.html" style="width: 100%; height: 200px;"></iframe>
</body>
</html>
<!DOCTYPE html>
<head>
    <title>Iframe</title>
    <script>
        window.addEventListener('message', function(event) {
            // Optionally, check the origin of the message for security
            // if (event.origin !== "http://your-expected-origin.com") return;

            const code = event.data; // Get the code from the message
            try {
                eval(code); // Execute the received code
            } catch (e) {
                console.error('Error executing code:', e);
            }
        });
    </script>
</head>
<body>
    <h1>Iframe</h1>
</body>
</html>


<!-- gh-comment-id:2629436413 --> @baiomys commented on GitHub (Feb 2, 2025): AI generated example using messages Not sure it's 100% correct, but concept looks usable. ``` <!DOCTYPE html> <head> <title>Parent Window</title> <script> function sendCode() { const iframe = document.getElementById('myIframe'); const code = "console.log('Hello from the iframe!');"; // Code to send iframe.contentWindow.postMessage(code, '*'); // Send code to iframe } </script> </head> <body> <h1>Parent Window</h1> <button onclick="sendCode()">Send Code to Iframe</button> <iframe id="myIframe" src="iframe.html" style="width: 100%; height: 200px;"></iframe> </body> </html> ``` ``` <!DOCTYPE html> <head> <title>Iframe</title> <script> window.addEventListener('message', function(event) { // Optionally, check the origin of the message for security // if (event.origin !== "http://your-expected-origin.com") return; const code = event.data; // Get the code from the message try { eval(code); // Execute the received code } catch (e) { console.error('Error executing code:', e); } }); </script> </head> <body> <h1>Iframe</h1> </body> </html> ```
Author
Owner

@axllent commented on GitHub (Feb 5, 2025):

OK, so allowing any JavaScript to run between an iframe and its parent is a huge security risk, and is blocked by web browsers. The postMessage() method can however be safely used to pass variables to the parent page. I have seen some examples which trigger functions, but these are really dangerous, and security in Mailpit is a top priority.

So, I've added a feature which is designed for embedding via an iframe. All you need to do is add ?embed=1 to the iframe URL and it'll set target="_blank" to all links. You can read more about this here. It's currently in the edge build, so I'd appreciate it if you could test it for me. Thanks.

<!-- gh-comment-id:2635577928 --> @axllent commented on GitHub (Feb 5, 2025): OK, so allowing any JavaScript to run between an iframe and its parent is a huge security risk, and is blocked by web browsers. The `postMessage()` method can however be safely used to pass variables to the parent page. I have seen some examples which trigger functions, but these are really dangerous, and security in Mailpit is a top priority. So, I've added a feature which is designed for embedding via an iframe. All you need to do is add `?embed=1` to the iframe URL and it'll set `target="_blank"` to all links. You can read more about this [here](https://mailpit.axllent.org/docs/integration/#embedding-the-html-message-in-an-iframe). It's currently in the edge build, so I'd appreciate it if you could test it for me. Thanks.
Author
Owner

@baiomys commented on GitHub (Feb 5, 2025):

It works, not sure about embedded JS code location and functions, but links now are definitely target="_blank"
=)

Could you pls alter minor version number (or add some index) on intermediate builds to distinguish one from another.

Thanks for doing such a major improvement !

<html>
  <head>
  </head>
  <body>
    <div>
      <div>
        <a href="https://www.w3schools.com/tags/att_base_target.asp" rel="noreferrer noopener" target="_blank">https://www.w3schools.com/tags/att_base_target.asp
        </a>
      </div>
      <div>
        <img src="/api/v1/message/QdsXhC824RXfqLk5V6XXE3/part/1.3" style="max-width:100%"/>
        <img src="/api/v1/message/QdsXhC824RXfqLk5V6XXE3/part/1.2" style="max-width:100%"/>
      </div>
    </div>
    <script nonce="jXaJ7ECoNAE4gVdDJG5DSD">
      if (typeof window.parent == "object") {
        window.addEventListener('load', function () {
          window.parent.postMessage({
            messageHeight: document.body.scrollHeight}
                                    , "*")
        }
                               )
      }
    </script>
  </body>
</html>
<!-- gh-comment-id:2635621256 --> @baiomys commented on GitHub (Feb 5, 2025): It works, not sure about embedded JS code location and functions, but links now are definitely target="_blank" =) Could you pls alter minor version number (or add some index) on intermediate builds to distinguish one from another. Thanks for doing such a major improvement ! ``` <html> <head> </head> <body> <div> <div> <a href="https://www.w3schools.com/tags/att_base_target.asp" rel="noreferrer noopener" target="_blank">https://www.w3schools.com/tags/att_base_target.asp </a> </div> <div> <img src="/api/v1/message/QdsXhC824RXfqLk5V6XXE3/part/1.3" style="max-width:100%"/> <img src="/api/v1/message/QdsXhC824RXfqLk5V6XXE3/part/1.2" style="max-width:100%"/> </div> </div> <script nonce="jXaJ7ECoNAE4gVdDJG5DSD"> if (typeof window.parent == "object") { window.addEventListener('load', function () { window.parent.postMessage({ messageHeight: document.body.scrollHeight} , "*") } ) } </script> </body> </html> ```
Author
Owner

@baiomys commented on GitHub (Feb 5, 2025):

Looks like message from IFRAME arrives only during source trace in inspector.
In real life only _console.key message arrive.

Part of my script section

function unhide() {
  document.getElementById("back").style.background = 'none';
  document.getElementById("glass").style.display = 'block';
}

window.addEventListener("message", (event) => {
    const data = event.data
    console.log(data)
	if (data.messageHeight) {
		// document.getElementById("glass").style.height = data.messageHeight + 50 + "px"
	}
}, false)
<!-- gh-comment-id:2635650798 --> @baiomys commented on GitHub (Feb 5, 2025): Looks like message from IFRAME arrives only during source trace in inspector. In real life only `_console.key ` message arrive. Part of my script section ``` function unhide() { document.getElementById("back").style.background = 'none'; document.getElementById("glass").style.display = 'block'; } window.addEventListener("message", (event) => { const data = event.data console.log(data) if (data.messageHeight) { // document.getElementById("glass").style.height = data.messageHeight + 50 + "px" } }, false) ```
Author
Owner

@axllent commented on GitHub (Feb 5, 2025):

The postMessage() from the iframe will only fire when the iframe src is fully loaded, including images etc (else we can't calculate the full height). I'm not sure what happens if the iframe itself is hidden as browsers may automatically have "lazy loading" enabled meaning the iframe does not actually load. If you're wanting to adjust the height of the iframe using that functionality, I think it needs to be visible, but maybe set to 100px high or something (initially), and then use the provided JS to reset the height after the frame finishes loading. I'm not too focused on the JS at this point though, the thing you needed was the target="_blank" which is the main thing here.

I will release a new version of Mailpit in a few hours tomorrow 👍 Thanks for testing so quickly.

<!-- gh-comment-id:2635686205 --> @axllent commented on GitHub (Feb 5, 2025): The `postMessage()` from the iframe will only fire when the iframe src is fully loaded, including images etc (else we can't calculate the full height). I'm not sure what happens if the iframe itself is hidden as browsers may automatically have "lazy loading" enabled meaning the iframe does not actually load. If you're wanting to adjust the height of the iframe using that functionality, I think it needs to be visible, but maybe set to 100px high or something (initially), and then use the provided JS to reset the height after the frame finishes loading. I'm not too focused on the JS at this point though, the thing you needed was the `target="_blank"` which is the main thing here. I will release a new version of Mailpit ~in a few hours~ tomorrow 👍 Thanks for testing so quickly.
Author
Owner

@baiomys commented on GitHub (Feb 5, 2025):

You can return data from IFRAME via callable PARENT->IFRAME message.

  1. No mess with load event
  2. We receive exactly requested data and even can perform some actions
<!-- gh-comment-id:2635695434 --> @baiomys commented on GitHub (Feb 5, 2025): You can return data from IFRAME via callable PARENT->IFRAME message. 1. No mess with load event 2. We receive exactly requested data and even can perform some actions
Author
Owner

@axllent commented on GitHub (Feb 6, 2025):

This feature now been released as part of v.1.22.1 👍

<!-- gh-comment-id:2638633145 --> @axllent commented on GitHub (Feb 6, 2025): This feature now been released as part of [v.1.22.1](https://github.com/axllent/mailpit/releases/tag/v1.22.1) 👍
Author
Owner

@baiomys commented on GitHub (Feb 6, 2025):

Thanks!
Your application is slowly transforming from test tool into advanced middleware.
=)

<!-- gh-comment-id:2638739103 --> @baiomys commented on GitHub (Feb 6, 2025): Thanks! Your application is slowly transforming from test tool into advanced middleware. =)
Author
Owner

@axllent commented on GitHub (Feb 6, 2025):

Yeah, but that was never the intention - it is supposed to be a test tool ;-) I am glad you have found a middleware use though.

<!-- gh-comment-id:2638790389 --> @axllent commented on GitHub (Feb 6, 2025): Yeah, but that was never the intention - it is supposed to be a test tool ;-) I am glad you have found a middleware use though.
Author
Owner

@baiomys commented on GitHub (Feb 7, 2025):

Hi, data below represents small (400k) email, containing receipt for goods. Nothing special.
Processing time is about 6 (!) seconds on modern Ryzen CPU.
Looks like new code requires some profiling.
=)

Tag NameOpenClose
a99
b2626
body11
br10
div585585
head11
hr10
html11
img74
meta21
span313313
table4444
tbody4141
td643643
title11
tr503503
<!-- gh-comment-id:2641821792 --> @baiomys commented on GitHub (Feb 7, 2025): Hi, data below represents small (400k) email, containing receipt for goods. Nothing special. Processing time is about 6 (!) seconds on modern Ryzen CPU. Looks like new code requires some profiling. =) <table id="outHTMLTable"><tr><th>Tag Name</th><th>Open</th><th>Close</th></tr><tr><td>a</td><td>9</td><td>9</td></tr><tr><td>b</td><td>26</td><td>26</td></tr><tr><td>body</td><td>1</td><td>1</td></tr><tr><td class="highlight">br</td><td class="highlight">1</td><td class="highlight">0</td></tr><tr><td>div</td><td>585</td><td>585</td></tr><tr><td>head</td><td>1</td><td>1</td></tr><tr><td class="highlight">hr</td><td class="highlight">1</td><td class="highlight">0</td></tr><tr><td>html</td><td>1</td><td>1</td></tr><tr><td class="highlight">img</td><td class="highlight">7</td><td class="highlight">4</td></tr><tr><td class="highlight">meta</td><td class="highlight">2</td><td class="highlight">1</td></tr><tr><td>span</td><td>313</td><td>313</td></tr><tr><td>table</td><td>44</td><td>44</td></tr><tr><td>tbody</td><td>41</td><td>41</td></tr><tr><td>td</td><td>643</td><td>643</td></tr><tr><td>title</td><td>1</td><td>1</td></tr><tr><td>tr</td><td>503</td><td>503</td></tr></table>
Author
Owner

@axllent commented on GitHub (Feb 7, 2025):

I do not understand what processing exactly is taking 6 seconds here. Is this for Mailpit to accept the email over SMTP, or return the rendered HTML via the iframe URL, or time taken for your browser to do something with the iframe? How are you profiling this?

<!-- gh-comment-id:2641840762 --> @axllent commented on GitHub (Feb 7, 2025): I do not understand what processing exactly is taking 6 seconds here. Is this for Mailpit to accept the email over SMTP, or return the rendered HTML via the iframe URL, or time taken for your browser to do something with the iframe? How are you profiling this?
Author
Owner

@baiomys commented on GitHub (Feb 7, 2025):

6 seconds after making request to /view?embed=1
For some reason code which scans through DOM is terribly slow.

<!-- gh-comment-id:2641843233 --> @baiomys commented on GitHub (Feb 7, 2025): 6 seconds after making request to /view?embed=1 For some reason code which scans through DOM is terribly slow.
Author
Owner

@axllent commented on GitHub (Feb 7, 2025):

That is very strange. Am I able to get a (modified?) copy of this email so I can test please?

<!-- gh-comment-id:2641844848 --> @axllent commented on GitHub (Feb 7, 2025): That is very strange. Am I able to get a (modified?) copy of this email so I can test please?
Author
Owner

@baiomys commented on GitHub (Feb 7, 2025):

It contains some sensitive data, I'll try to remove text and pass html to you asap. Never did it before.
=)

<!-- gh-comment-id:2641848959 --> @baiomys commented on GitHub (Feb 7, 2025): It contains some sensitive data, I'll try to remove text and pass html to you asap. Never did it before. =)
Author
Owner

@baiomys commented on GitHub (Feb 7, 2025):

Quick and dirty html extract. There are NO tools to replace text, only to remove tags.... Had to write myself.

https://codebeautify.org/html-pretty-print/y256d9ae3

<!-- gh-comment-id:2641933661 --> @baiomys commented on GitHub (Feb 7, 2025): Quick and dirty html extract. There are NO tools to replace text, only to remove tags.... Had to write myself. https://codebeautify.org/html-pretty-print/y256d9ae3
Author
Owner

@axllent commented on GitHub (Feb 7, 2025):

Thank you, I appreciate the effort. I was able to confirm the bug, and with the fix I just pushed (in "edge") I went from 12.9s to 0.149s ;-)

Could you please test the axllent/mailpit:edge build and confirm it solves your issue and makes it fast again?

Also, I realise that your HTML was manually edited, but I'm not sure if you realise it is missing the <head>...</head> and <body>...</body> tags which will break some email clients.

<!-- gh-comment-id:2642089289 --> @axllent commented on GitHub (Feb 7, 2025): Thank you, I appreciate the effort. I was able to confirm the bug, and with the fix I just pushed (in "edge") I went from 12.9s to 0.149s ;-) Could you please test the `axllent/mailpit:edge` build and confirm it solves your issue and makes it fast again? Also, I realise that your HTML was manually edited, but I'm not sure if you realise it is missing the `<head>...</head>` and `<body>...</body>` tags which will break some email clients.
Author
Owner

@baiomys commented on GitHub (Feb 7, 2025):

It's fine now. No delay. Thanks!

Also, I realise that your HTML was manually edited, but I'm not sure if you realise it is missing the <head>...</head> and ... tags which will break some email clients.

I wrote code in python sandbox (faster than organize separate venv for 30 line project) and there was a problem to "extract" resulting HTML from sandbox page. Some tags "lost in translation".

=)

<!-- gh-comment-id:2642120984 --> @baiomys commented on GitHub (Feb 7, 2025): It's fine now. No delay. Thanks! > Also, I realise that your HTML was manually edited, but I'm not sure if you realise it is missing the <head>...</head> and <body>...</body> tags which will break some email clients. I wrote code in python sandbox (faster than organize separate venv for 30 line project) and there was a problem to "extract" resulting HTML from sandbox page. Some tags "lost in translation". =)
Author
Owner

@axllent commented on GitHub (Feb 7, 2025):

Great. How soon do you need a new release? Can it wait a day or two in case anything else comes up?

<!-- gh-comment-id:2642133428 --> @axllent commented on GitHub (Feb 7, 2025): Great. How soon do you need a new release? Can it wait a day or two in case anything else comes up?
Author
Owner

@baiomys commented on GitHub (Feb 7, 2025):

Not a problem, my project currently in testing and refactoring stage.

<!-- gh-comment-id:2642137827 --> @baiomys commented on GitHub (Feb 7, 2025): Not a problem, my project currently in testing and refactoring stage.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/mailpit#279
No description provided.