I Just Need the App to Send an Email

AI can help me write the code. I still own what it becomes.

“I just need the app to send an email.”

That sounds simple enough.

And, to be fair, sending email isn’t exactly new territory for me.

I’ve used transactional email services for years. For a long time, SendGrid was my go-to. I connected applications and services using API keys, and I’ve also used it behind Postfix on Ubuntu to provide a local SMTP relay.

I even wrote about that back in May 2024:

Creating a Local SMTP Relay Using Postfix and SendGrid

For a long time, SendGrid worked really well for what I needed. The free tier was more than enough for my relatively modest email requirements.

After SendGrid was acquired by Twilio, though, my experience with the service changed. That’s entirely my personal experience and opinion, but eventually I decided to look elsewhere.

Moving to SMTP2GO

I tried quite a few transactional email services.

There are certainly plenty to choose from.

Eventually I settled on SMTP2GO.

For me, it was simply easier.

The initial setup was straightforward, adding and verifying domains was simple, managing users made sense, and I could get from creating an account to actually sending authenticated email without fighting the platform.

The free tier covered everything I needed for quite some time.

In fact, it worked so well that I’ve now reached the point where my usage has outgrown the free allowance and I pay around $15 per month for the next tier.

That’s probably a reasonable endorsement in itself.

If I had one complaint, it would be that the “remember me for seven days” option when logging into their portal doesn’t always seem to remember me for seven days.

But that’s a login annoyance rather than a criticism of the email service itself.

And then I started vibe coding.

The application needs to send an email

One of the applications I was building needed email.

Nothing particularly unusual.

Things like:

  • Password reset emails
  • Job completion notifications
  • Report completion notifications
  • Security or login failure notifications
  • Verification emails
  • Secure login links

All fairly standard application functionality.

And I already had SMTP2GO.

So this should be easy.

Give the application an SMTP account, write a few lines of code and send the message.

Done.

Except…

How does the application authenticate?

That’s where something seemingly trivial starts becoming much more interesting.

SMTP credentials or an API key?

At its simplest, my application needs permission to send an email.

With a service such as SMTP2GO, I can achieve that in a couple of ways.

I could give the application SMTP credentials and have it authenticate to the SMTP service.

Or I could use an API and authenticate using an API key.

Either approach solves the immediate problem:

The application can now send email.

But it also creates another problem.

The application now has a credential.

And credentials have to live somewhere.

Where does the secret go?

This is where my security architect brain starts getting in the way of my vibe coding.

The AI can quite happily generate something like:

SMTP_USERNAME=someuser
SMTP_PASSWORD=somepassword

Or:

SMTP_API_KEY=xxxxxxxxxxxxxxxx

Technically, that works.

But where am I going to store it?

Certainly not hard-coded into the application.

And definitely not committed into a public GitHub repository.

Perhaps I put it in an environment variable.

That’s better.

But now I have another set of questions.

  • Where is that environment variable configured?
  • Who can read it?
  • How is it deployed?
  • How do I change it?
  • What happens when I need to rotate the credential?
  • Do I use the same credential in development, testing and production?
  • What happens if it is accidentally exposed?

Suddenly:

“I need the app to send an email” has become an identity and secrets-management problem.

And this is one of the things I’m finding fascinating about building software again.

The code isn’t necessarily the difficult bit

Getting AI to generate code capable of sending an email is remarkably easy.

I can describe what I want and, seconds later, have something that probably works.

But “it works” isn’t the same as “I should deploy it.”

That distinction becomes increasingly important as the application grows.

The AI doesn’t own the credential.

I do.

The AI doesn’t have to respond when an API key appears in a GitHub repository.

I do.

The AI doesn’t decide whether development and production should share credentials.

I do.

And the AI isn’t ultimately responsible for the architecture.

I am.

That’s where nearly 30 years of experience starts becoming rather useful.

One requirement creates another

This is the pattern I’m beginning to see repeatedly with vibe coding.

The initial requirement is usually tiny:

Send an email.

Then that requirement creates another:

Authenticate to the email service.

Which creates another:

Store the credential securely.

Then:

Separate configuration between environments.

Then:

Rotate and revoke credentials.

Then:

Monitor their use.

And eventually:

Should this even be a long-lived credential at all?

The interesting part isn’t necessarily writing the code.

It’s understanding the chain of architectural decisions created by that code.

From a few lines of code to architecture

This is probably one of the biggest things I’ve learned since I started building again.

AI has dramatically reduced the friction involved in creating software.

Something that might once have required me to spend an evening reading documentation, looking through examples and working out the syntax can now start with a conversation:

“I need the application to send an email when the job completes.”

A few moments later, I can have working code.

That’s incredibly powerful.

But AI reducing the friction involved in writing the code doesn’t remove the architectural decisions created by that code.

If anything, it can make those decisions arrive faster.

I can add functionality far more quickly than I could have done before.

Which means I can also introduce dependencies, credentials, services and security decisions far more quickly than I could have done before.

And that’s something I’m becoming increasingly conscious of.

The barrier to creating software has dropped enormously.

The responsibility for what we create hasn’t.

Sending the email wasn’t really the interesting part

In the end, sending the email wasn’t particularly difficult.

SMTP2GO did exactly what I needed it to do.

The interesting part was everything that surrounded it.

A requirement that sounded like:

“Send me an email when this finishes.”

quickly became a conversation about:

Authentication.

Credentials.

Secrets.

Configuration.

Environment separation.

Deployment.

Security.

And that’s exactly why I’m enjoying building software again.

I’m writing code, but I’m also finding myself applying years of infrastructure, cloud, identity and security experience to problems I previously encountered from the other side.

AI might be helping me build the application.

But I still have to decide what that application becomes.

And email was only the beginning.

Because once I had an application that could do useful things and notify me when they happened, another thought occurred to me.

Hang on… what if other people could log in and use this too?

That opened an entirely different rabbit hole.


What’s next?

In Part 3 of Building Again, I’ll look at what happened when a personal application started becoming something other people could use.

That meant users.

And users meant authentication, authorisation, sessions, data separation and a whole new set of security decisions.

Because apparently I couldn’t just leave it as a little app running for myself.