How to remove portal.min.js but keep email subscription functionality?

What’s missing from your proposed flow is that subscribers confirm their emails. I know I’m not answering the question you asked, but humor me for a moment while I tell you a story:

A friend scored 'hislastname@gmail.com` as his email address. Someone he doesn’t know (whose gmail address is probably very similar) keeps entering his address into websites. He gets unwanted bulk mail, but he has also gotten concert tickets, gift cards, password reset requests, insurance information, online bills, etc. For a while, he tried contacting the legit-looking businesses to tell them they had a mistake. Now he just reports them as spam.

A decade ago, someone put my email into a “sell your house fast” type website that was clearly selling its data. Typo or a prank, I don’t know. To this day, I’m still getting spammy emails asking whether I still want to sell the house. (I suspect typo, because it looks like a legit house address four hours away.) I tried hitting the unsubscribe button for a while, but now I’m just reporting them as spam.

So really, before you send that newsletter, you’ve got to make sure it’s a legit email address and they want your stuff.

Happily, the only part of email opt-in that the portal code handles is the happy Success ‘toast’/badge that briefly appears when a user clicks the link in their email.

So, back to your question, how can you get rid of portal but keep the subscription button working? Basically, you just need a POST request to the /send-magic-link endpoint that includes the user’s email address (and optionally labels and name). This code looks like it does that, but minified code in a scrolling window is pretty hard to read… I think you’re maybe missing some function definitions.

You might instead try looking at the new embeddable sign-up widget (available under settings > membership > portal) - that’s much simpler. Mine (minimal) looks like:

<div style="min-height: 58px;max-width: 440px;margin: 0 auto;width: 100%">
<script src="https://cdn.jsdelivr.net/ghost/signup-form@~0.1/umd/signup-form.min.js" data-button-color="#66298e" data-button-text-color="#FFFFFF" data-site="https://demo.spectralwebservices.com" async>
</script>
</div>

… but then you’re loading this other code that isn’t super small either. Hrm. Let me look, I think I’ve got working code that does what you want without loading React!

Yeah, here’s some code that does basically what you want:

<form>
  <div class='form-group'>
    <label for='subscribe-email' >{{t 'Your email address' }}</label>
    <input type='email' name='email' id='subscribe-email' placeholder='{{t 'Your email address' }}'>
    <button value='{{t 'Subscribe' }}' class='subscribe-button'>{{t 'Subscribe' }}</button>
  </div>

  <div class='alert--success'>{{t 'Please check your inbox and click the link to confirm your subscription.' }}</div>
  <div class='alert--invalid'>{{t 'Please enter a valid email address' }}</div>
  <div class='alert--error'>{{t 'An error occurred, please try again later.' }}</div>
</form>
<script>
      async function sendMagicLink(name, email) {
        let body = await JSON.stringify({
            "email": email,
            "emailType": "signup",
            "urlHistory": [],
            "name": name
            })
        let response = await fetch('/members/api/send-magic-link/', {"headers": {"content-type": "application/json"}, "body": body, "method": "POST"})
        // there should really be some error handling here, but instead I'm just sending the reader to another page. If you're not doing that, you'd read the response and decide which message above to reveal. // 
        window.location.href='/signup-2/?email=' + encodeURIComponent(email)

    }

  document.querySelector('.subscribe-button').addEventListener('click', (e) => {
      e.preventDefault()
      sendMagicLink('',document.querySelector('#subscribe-email').value)
  })
</script>