Hacker News new | past | comments | ask | show | jobs | submit login
Making a newsletter backend (axleos.com)
39 points by codyd51 on Sept 10, 2023 | hide | past | favorite | 15 comments



If you want the same thing with more controls and less building/maintenance, you can run Ghost on Digital Ocean for $5 a month


https://sendy.co/ is also a fantastic option in this area. It wasn't quite for us in the end (we have quite complex needs) but I gave it a good workout and it's pretty neat.


Great! Thanks for sharing


Digital Ocean can eat dirt for buying CSS Tricks just to send it to the graveyard.

Use Vultr, or Linode if you have to :)


I saw Google App Engine when skimming and had to check the date. The last time I launched a GAE project was in 2012.


Hello, author here! I used GCP as it was familiar and quick to set up on that basis — we use it as our cloud provider at my day job — but I’ve got no special allegiance otherwise. I’m curious what you would’ve expected in its place? A corresponding AWS offering? A more ‘bare’ hosted VM from a commodity provider?


Sorry for drive-by (accidental) criticism, it was really more of a joke than anything. I haven't kept up to date at all and I think I missed that GAE is now a general-purpose hosting platform now (iirc in 2012 it was specific to a Google-provided framework). I think I'd use GAE now on further looks, or I guess something like Render if I didn't want to use GCP.


Hey if it works, it works. GAE still has a generous free tier. Most new stuff I’ve seen has been migrating to Cloud Run for more configuration and options.


If you are using GCP Cloud Run services are very nice


Anyone know of any good cheap options for making a newsletter frontend that looks nearly professional-media quality? The closest thing I can find is Ghost and Wordpress templates that are nearly $100 to license.


Maybe some of the themes on https://jamstackthemes.dev/ might work for you?


Try Ghost's free headline theme, or if you want something really good splurge for a Ghost theme from Bright Themes.


> Some JavaScript on this site that’ll ask the backend to sign up a new user.

This makes me sad. The browser has perfectly good form submission stuff that doesn’t need JavaScript, and there’s seldom any actual reason to require JavaScript. Yet I find it increasingly common that web developers, despite perhaps (hopefully!) still using <form> and <input type=submit>, are somehow unaware of this and assume JavaScript must be used, and use JSON encoding, and trip up over CORs, and… and… Whereas in fact, you only need JavaScript to support seamless content changes based on the form submission (providing feedback or whatever without loading a new version of the page with just that alteration), and it’s normally perfectly reasonable to support normal form submission with just a slight enhancement if JavaScript runs.

Here’s an alteration of the newsletter form, which assumes that /subscribe is a regular HTTP form submission endpoint, accepting application/x-www-form-urlencoded form data and returning a basic message as an HTML fragment (or even plain text):

  <div class="hover-div blog-post-container pastel-depth-4 lift-on-hover" id="newsletter_signup" style="border-radius: 14px;">
  <h3 style="margin-top: 6px">Newsletter</h3>
  <form id="sign_up_for_newsletter_form" method="post" action="https://axleos-blog-newsletter.ew.r.appspot.com/subscribe" target="_blank">
  [… seven unchanged lines omitted for brevity …]
  </form>
  <script>
      sign_up_for_newsletter_form.addEventListener("submit", async function(event) {
          event.preventDefault();
          let response = await fetch(this.action, {
              method: "POST",
              body: new URLSearchParams(new FormData(this)),
          });
          newsletter_signup.innerHTML =
              response.ok
                  ? "<p>Signup successful!</p>"
                  : `<p>Signup failed: ${response.statusText} / ${await response.text()}<p>`;
      });
  </script>
A few notes:

• I’ve added the method, action and target attributes to the <form> element. The consequence of this is that regular form submission, if the JavaScript isn’t run for whatever reason, will submit the data to that URL in a new window, the idea being that the user just gets a new page containing “signup successful” and nothing else. (By contrast, on the current page, submitting the form will submit to the current URL with ?email=… added, appearing to the user as though the page has just reloaded, and it won’t be clear whether or not it worked.)

• I’ve shifted the <script> element from before the form to after it, so that the form exists when the script is run, so that it can do its thing immediately rather than being deferred until the page finishes loading by wrapping it in $(document).ready(function(){…}) (or addEventListener("DOMContentLoaded", function(){…}) would be non-jQuery equivalent, so long as it’s still an inline script). This is very desirable if the scripts are inline anyway, and quantities like this should basically always be inline for the best performance (seriously, inlining all your JS and CSS yields the best performance until surprisingly large sizes—far past 100 KB in practically all deployment and access scenarios, even with a warm cache). You only want ready dances if you’re deliberately loading the script asynchronously (which I will also defend as not unreasonable for something like this: the document is fully interactive without it, it can run later or even not at all and cause no problem).

• I’ve removed jQuery usage because it’s simply not necessary ($("#…") → document.getElementById("…"), .on() → .addEventListener(), $.ajax() → fetch()); all the native alternatives have excellent browser support. I’ve also used native async/await because it makes things a tad nicer. (It’s not a compatibility hazard, because ancient browsers will just call it a syntax error and not execute the script, and thus get the no-JS regular form submission behaviour.)

• I dislike this use of innerHTML with the response text; a little too fragile if you try sending plain text but it ends up including HTML syntax, or if you can get things like unexpected server error HTML from Google App Engine or gunicorn or whatever and now you’re dropping a whole <html>…</html> in there with unknown contents. But I’m matching the original.

• I’ve written just sign_up_for_newsletter_form and newsletter_signup with no document.getElementById("…") wrapping just for the fun of it, showing off my favourite HTML+JavaScript golfing technique. It’s a matter of very ancient history, but part of the spec <https://html.spec.whatwg.org/multipage/nav-history-apis.html...> and it works everywhere. Decide for yourself whether it’s wise. (Personally I think the concerns about it in cases like these are somewhat overblown, but at the very least it’s certainly a technique that should not be scaled.)

• The `new URLSearchParams(…)` wrapping is to get application/x-www-form-urlencoded, for consistency with normal form submission. Just `body: new FormData(this)` uses multipart/form-data encoding, which regular form submission will only use if you’re uploading files via <input type="file">. A little janky, in my opinion, being different from normal form submission, even if simply splitting on type makes a deal of sense. Anyway, all serious HTTP server libraries will support both encodings of form data, so you aren’t likely to need it, but I figured I’d make it consistent anyway, because otherwise experience says mistakes get made. The goal is to minimise the differences between the no-JS and JS pathways, to minimise the chances of the no-JS way accidentally breaking.

• I replaced the button click handler with a form submit handler. Personal preference, to a considerable extent, with no actual practical difference since the button is an <input type="submit">. (Pressing Enter from the email field will trigger clicking on the first submit button: <https://html.spec.whatwg.org/multipage/form-control-infrastr...>.)

• I hold out hope that eventually we might get some kind of auto-resizing iframe (one starting point on the topic: <https://github.com/whatwg/html/issues/555>), so that you could put a response iframe below the form and make it the target. This exposes another approach: put the form inside an iframe, and then just use normal submission entirely. The only problem (and it’s not a tiny problem, unfortunately) is this matter of sizing the iframe.

—⁂—

I would also suggest adding a simple honeypot field to the form: https://news.ycombinator.com/item?id=37058847


Really interesting and actionable, thanks for the information and for the example code!

I don't have any web development expertise, and it's not where my interests lie, so I've been following a "google it and move on as quickly as possible" approach. That said, since I'm now making web tools, I do have an intellectual responsibility to learn the platform. I reached for jQuery just because I had a vague sense that it was a "web development thing", and didn't bother to learn the fundamentals of what exactly it was bridging, and whether those bridges were still relevant in today's browser landscape. I appreciate the callout, and will use it as an opportunity to learn more about the web platform.

Like you realised, I thought JavaScript was appropriate because I wanted to ensure that the user wouldn't be navigated to a new page when they submitted the form. I appreciate the quite tidy approach you've shared that achieves this when JS is available, and still does something reasonable when it's not!


You might like HTMX which essentially does the above with some declarative tags and a small JS library. It’s surprisingly powerful.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: