docs: add Elixir ZeroSMTP example - #85
Conversation
msgwing
left a comment
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
Thanks for this — the example follows the same environment-variable convention as the others and CI is green across all languages. Merging.
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>
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>
|
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:
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. |
`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.
`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.
|
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! |
|
@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:
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, |
Closes #25.
Adds
elixir-zerosmtp.exs, a no-Mix script that authenticates and sends throughmx.msgwing.com:465over SSL using Elixir/OTP built-ins. Adds rows to both README tables and an Elixir syntax-check job tolint.yml.