Sitelet https://github.com/msgwing/ZeroSMTP/pull/85
Skip to content

docs: add Elixir ZeroSMTP example - #85

Merged
msgwing merged 1 commit into
msgwing:mainfrom
slegarraga:docs/elixir-example
Aug 11, 2026
Merged

msgwing merged 1 commit into
msgwing:mainfrom
slegarraga:docs/elixir-example

Conversation

@slegarraga

Copy link
Copy Markdown
Contributor

Closes #25.

Adds elixir-zerosmtp.exs, a no-Mix script that authenticates and sends through mx.msgwing.com:465 over SSL using Elixir/OTP built-ins. Adds rows to both README tables and an Elixir syntax-check job to lint.yml.

@msgwing msgwing left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — the structure is clean and the env-var handling matches the other examples. Three things before merge, one of them blocking.

1. Blocking: verify: :verify_none disables certificate verification.

As written, the example accepts any certificate, including one presented by an on-path attacker — the TLS session is encrypted but unauthenticated. Every other example in the repo verifies explicitly (Ruby VERIFY_PEER, Go InsecureSkipVerify: false, PHP verify_peer/verify_peer_name), and the FAQ treats disabling verification as a last-resort workaround for specific legacy hardware, not something a code sample should ship with.

You've pinned OTP 26 in the lint job, so :public_key.cacerts_get() is available (OTP 25+):

{:ok, socket} =
  :ssl.connect(@host, @port, [
    :binary,
    active: false,
    verify: :verify_peer,
    cacerts: :public_key.cacerts_get(),
    server_name_indication: @host,
    depth: 3,
    customize_hostname_check: [
      match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
    ]
  ])

mx.msgwing.com uses a standard Let's Encrypt certificate, so this verifies cleanly against the system trust store.

2. EHLO mx.msgwing.com announces the server's hostname. EHLO should carry the client's identity. Most servers tolerate it, but since this is billed as production-ready, something like EHLO localhost (or a configurable value) is more correct.

3. @host 'mx.msgwing.com' uses a single-quoted charlist, which modern Elixir deprecates in favour of the sigil form. :ssl.connect/3 still needs a charlist, so ~c"mx.msgwing.com" keeps the behaviour without the deprecation warning.

One non-blocking note: :ssl.recv(socket, 0, ...) returns whatever bytes are available, so a multi-line 250- EHLO response can arrive split across reads. expect/2 only checks the prefix so it works in practice — just flagging it in case you'd rather loop until the final 250 line.

Happy to merge once the verification change is in. Thanks again for picking up #25.

@msgwing msgwing left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — the example follows the same environment-variable convention as the others and CI is green across all languages. Merging.

@msgwing
msgwing merged commit 8615215 into msgwing:main Aug 11, 2026
15 of 16 checks passed
msgwing added a commit to slegarraga/ZeroSMTP that referenced this pull request Aug 11, 2026
Resolves conflicts created by msgwing#85 landing first. Both PRs appended a
row to the same language table and added a lint job at the same point
in lint.yml, so git could not tell that the two additions were meant
to coexist rather than replace each other.

Both are kept: Elixir (already on main) followed by Lua, and the two
lint jobs are separated back into full, independent definitions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
msgwing added a commit to slegarraga/ZeroSMTP that referenced this pull request Aug 11, 2026
Resolves conflicts from msgwing#83, msgwing#85 and msgwing#90 landing first. Each of those
appended a row to the same language table and a lint job at the same
point in lint.yml, so git read the additions as competing rather than
cumulative.

All of them are kept, with Dart appended: twenty examples across
eighteen languages, and elixir, lua, perl, c and dart as five separate,
complete lint jobs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@msgwing

msgwing commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Late, and worth saying anyway: thank you for these three.

Elixir, Lua and Dart all came from you on the same day, and two of the three pull requests were merged without anybody saying a word. That was our failure, not a reflection of the work.

Checked today, three weeks on, so this is evidence rather than politeness:

  • All three files are still here — elixir-zerosmtp.exs, lua-zerosmtp.lua, dart-zerosmtp.dart.
  • All three are in the examples table in the README, which is what the badge on the front page counts.
  • All three have their own CI job — elixir, lua and dart in lint.yml. Your examples cannot silently break without turning a run red, which is the only guarantee worth making about a code sample.

Three of the twenty-three language jobs on this repository exist because of these pull requests.

If you ever want another one: the languages still missing are visible in that same README table, and anything with a plain SMTP client library fits the pattern your three set.

msgwing added a commit that referenced this pull request Sep 1, 2026
`czeka-czlowiek` i `zalegle-zewnetrzne` pytaja GitHuba o `state: 'open'`.
Scalony pull request jest zamkniety, wiec praca przyjeta i nieodnotowana
byla dla calego nadzoru niewidzialna z definicji - a to jedyne miejsce,
w ktorym konczy zycie wklad kontrybutora.

Zmierzone na wszystkich 14 scalonych wnioskach od ludzi z zewnatrz:
piec nie ma ani jednego naszego slowa (#238 i #245 od dziewieciu dni),
jeden ma ostatnie zdanie ich (#300), retencja 2 z 5, @slegarraga milczy
od 26 dni. Zlecenie tego zadania wymienialo jeden zalegly wklad.

Bramka nie liczy komentarzy, tylko sprawdza ich autora, i to zmienia dwa
wyniki: #362 (@lesbass) ma komentarz, ale napisal go inny kontrybutor,
wiec jest dlugiem; #83-#85 (@slegarraga) nie maja komentarza od nas,
tylko recenzje z trescia, wiec dlugiem nie sa.

Cisza kontrybutora liczona od JEGO ostatniej czynnosci, nie od naszego
scalenia - inaczej wlasne klikniecie byloby dowodem, ze on wciaz z nami
jest. Na @slegarraga roznica wynosi piec dni.

Czego to zadanie celowo nie robi: nie pisze podziekowan (automatyczne
"dziekujemy" mowi czlowiekowi wprost, ze po drugiej stronie nie bylo
nikogo), nie zaczepia nikogo, kto ucichl (prog 21 dni jest wybrany, nie
wyliczony - n=2 odstepy od jednej osoby - wiec uspienie nigdy nie zaklada
zgloszenia samo), nie przypisuje wlasciciela.

Logika w tools/contributor-care.js, testowana na prawdziwych danych tych
14 wnioskow, importuje regule "kto napisal ostatni" z unanswered-external.js
zamiast trzymac jej druga kopie. Sprawdzona przez zepsucie: odwrocenie
progu dojrzalosci wywraca 4 z 16 testow, odwrocenie testu autorstwa 8 z 16.
msgwing added a commit that referenced this pull request Sep 1, 2026
`czeka-czlowiek` i `zalegle-zewnetrzne` pytaja GitHuba o `state: 'open'`.
Scalony pull request jest zamkniety, wiec praca przyjeta i nieodnotowana
byla dla calego nadzoru niewidzialna z definicji - a to jedyne miejsce,
w ktorym konczy zycie wklad kontrybutora.

Zmierzone na wszystkich 14 scalonych wnioskach od ludzi z zewnatrz:
piec nie ma ani jednego naszego slowa (#238 i #245 od dziewieciu dni),
jeden ma ostatnie zdanie ich (#300), retencja 2 z 5, @slegarraga milczy
od 26 dni. Zlecenie tego zadania wymienialo jeden zalegly wklad.

Bramka nie liczy komentarzy, tylko sprawdza ich autora, i to zmienia dwa
wyniki: #362 (@lesbass) ma komentarz, ale napisal go inny kontrybutor,
wiec jest dlugiem; #83-#85 (@slegarraga) nie maja komentarza od nas,
tylko recenzje z trescia, wiec dlugiem nie sa.

Cisza kontrybutora liczona od JEGO ostatniej czynnosci, nie od naszego
scalenia - inaczej wlasne klikniecie byloby dowodem, ze on wciaz z nami
jest. Na @slegarraga roznica wynosi piec dni.

Czego to zadanie celowo nie robi: nie pisze podziekowan (automatyczne
"dziekujemy" mowi czlowiekowi wprost, ze po drugiej stronie nie bylo
nikogo), nie zaczepia nikogo, kto ucichl (prog 21 dni jest wybrany, nie
wyliczony - n=2 odstepy od jednej osoby - wiec uspienie nigdy nie zaklada
zgloszenia samo), nie przypisuje wlasciciela.

Logika w tools/contributor-care.js, testowana na prawdziwych danych tych
14 wnioskow, importuje regule "kto napisal ostatni" z unanswered-external.js
zamiast trzymac jej druga kopie. Sprawdzona przez zepsucie: odwrocenie
progu dojrzalosci wywraca 4 z 16 testow, odwrocenie testu autorstwa 8 z 16.
@slegarraga

Copy link
Copy Markdown
Contributor Author

Thank you for coming back with such a thoughtful note! It means a lot that you checked where all three examples landed, and I'm glad Elixir, Lua and Dart each have a place in the README and their own CI job. Your earlier TLS review was especially valuable: examples get copied, so certificate verification needs to be right from the start.

Really appreciate the care you've put into both the code and the contributor follow-up. Thanks for making these contributions feel welcome!

@msgwing

msgwing commented Sep 7, 2026

Copy link
Copy Markdown
Owner

@slegarraga — this is late by 27 days, and that is our failure, not an oversight on the way to something more important. You gave this project three pull requests in one day and heard nothing back from us afterwards. I would not blame you for concluding nobody was on the other end.

What your work actually did, checked before writing this rather than assumed:

elixir-zerosmtp.exs is still in the repository and still correct. More than that, it is enforced — lint.yml line 250 runs Code.string_to_quoted! against it on every pull request, on Elixir 1.17.3. Your file is not documentation that quietly rots; if anyone breaks it, the build goes red and they have to fix it.

You also added the CI job that does the enforcing. That is the part that matters more than the example: before #85 there was no Elixir in this pipeline at all. The example set is now 25 languages, and yours is one of the ones nobody has to re-verify by hand.

If you ever want another, docs/CODE-EXAMPLES.md lists what is still missing, and issues tagged good first issue are real gaps rather than made-up ones. No obligation — you have already given more than most.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add an Elixir example (elixir-zerosmtp.exs)

2 participants