<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: François</title>
    <description>The latest articles on DEV Community by François (@fralps).</description>
    <link>https://dev.to/fralps</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F351870%2Fb63c6800-3672-4e52-b69b-2d84fbc95246.png</url>
      <title>DEV Community: François</title>
      <link>https://dev.to/fralps</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fralps"/>
    <language>en</language>
    <item>
      <title>Building AI-Powered Dream Analysis with Mistral AI in My Rails API</title>
      <dc:creator>François</dc:creator>
      <pubDate>Tue, 02 Jun 2026 11:46:35 +0000</pubDate>
      <link>https://dev.to/fralps/building-ai-powered-dream-analysis-with-mistral-ai-in-my-rails-api-1nhd</link>
      <guid>https://dev.to/fralps/building-ai-powered-dream-analysis-with-mistral-ai-in-my-rails-api-1nhd</guid>
      <description>&lt;p&gt;Pastel is a project I built alone — an API where users log their sleep experiences: dreams, nightmares, lucid sleeps, and the emotional patterns that come with them. Recently, I decided to push it further. What if the app could analyze those dreams for you? What if it could look at the description, the mood, the tags, and tell you something meaningful about what your dream might be saying?&lt;/p&gt;

&lt;p&gt;So I built it. Using &lt;a href="https://mistral.ai/" rel="noopener noreferrer"&gt;Mistral AI&lt;/a&gt;, background jobs, and a whole lot of defensive coding. Here's how I did it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Wanted
&lt;/h2&gt;

&lt;p&gt;The flow I had in mind was simple: a user logs a sleep entry with a title, a description, a sleep type (lucid, nightmare, recurring), tags, their current mood, and the intensity of the experience. From all that context, I wanted to return a &lt;strong&gt;structured, insightful interpretation&lt;/strong&gt; — and in their language, not just English.&lt;/p&gt;

&lt;p&gt;The hard part wasn't the API call. The hard part was making it reliable, testable, and production-ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture I Settled On
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;POST /api/v1/sleeps/:id/analyse
│
▼
┌──────────────────────┐
│ SleepsController │ ← I validate status, enqueue the job
│ #analyse │
└──────────────────────┘
│
▼ (enqueue)
┌──────────────────────┐
│ SleepAnalyseJob │ ← Background job via Solid Queue
│ perform() │
└──────────────────────┘
│
▼ (call)
┌──────────────────────┐
│ SleepAnalysisService │ ← I call Mistral, parse the response
│ call() │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ Mistral AI API │ ← magistral-small-2509 model
│ /chat/completions │
└──────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a classic Rails async pattern. The controller validates and enqueues, the background job orchestrates, and a service object encapsulates the business logic. Let me walk you through each layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Problem: How Do I Track the Analysis Lifecycle?
&lt;/h2&gt;

&lt;p&gt;Dream analysis isn't instantaneous. I needed a way to track where each request was in its lifecycle. I could have just used a boolean — analyzed or not — but that felt fragile. What if the API times out? What if the job crashes? What if the user spams the endpoint?&lt;/p&gt;

&lt;p&gt;So I added a proper three-state enum to the &lt;code&gt;Sleep&lt;/code&gt; model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ANALYSIS_STATUS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;not_started: &lt;/span&gt;&lt;span class="s1"&gt;'not_started'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;in_progress: &lt;/span&gt;&lt;span class="s1"&gt;'in_progress'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;done: &lt;/span&gt;&lt;span class="s1"&gt;'done'&lt;/span&gt;
&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

&lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="ss"&gt;:analysis_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ANALYSIS_STATUS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="s1"&gt;'not_started'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And two explicit state-transition methods so nothing happens by accident:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mark_as_analysis_not_started&lt;/span&gt;
  &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;analysis_status: :not_started&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mark_as_analysis_done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;analysis: &lt;/span&gt;&lt;span class="n"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;analysis_status: :done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also encrypted the &lt;code&gt;analysis&lt;/code&gt; field alongside the other sensitive data (&lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;current_mood&lt;/code&gt;). Dream analysis results are deeply personal — they deserve the same protection as the original dream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;encrypts&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:current_mood&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:analysis&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the &lt;a href="https://guides.rubyonrails.org/active_record_encryption.html" rel="noopener noreferrer"&gt;Rails Active Record Encryption docs&lt;/a&gt; for more on this feature.&lt;/p&gt;

&lt;p&gt;Finally, a scope for my dashboard so I can track how many dreams have been analyzed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:ai_analyzed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;not&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;analysis: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;analysis_status: :done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Controller: Idempotency Is My Obsession
&lt;/h2&gt;

&lt;p&gt;When I wrote the &lt;code&gt;#analyse&lt;/code&gt; action, the first thing I thought about was: what happens if someone calls this twice? Or if a mobile app retries the request because of a slow connection?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyse&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analysis_status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'done'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
     &lt;span class="vi"&gt;@sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analysis_status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'in_progress'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
     &lt;span class="vi"&gt;@sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s1"&gt;'Sleep analysis already in progress or done'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;code: &lt;/span&gt;&lt;span class="s1"&gt;'analysis_already_in_progress_or_done'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;status: :ok&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:locale&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_locale&lt;/span&gt;
  &lt;span class="no"&gt;SleepAnalyseJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="vi"&gt;@sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analysis_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:in_progress&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="no"&gt;SleepSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@sleep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;view: :update_and_show&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s1"&gt;'Failed to start sleep analysis'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;code: &lt;/span&gt;&lt;span class="s1"&gt;'analysis_start_failed'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
           &lt;span class="ss"&gt;status: :unprocessable_content&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three decisions here that I'm proud of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency everywhere&lt;/strong&gt;. I check the status enum AND the presence of an existing analysis. The endpoint can be called a hundred times — only the first one does anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locale passthrough&lt;/strong&gt;. The locale travels from the request, through the job, into the service, and directly into the Mistral prompt. The AI responds in the user's language.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimistic status update&lt;/strong&gt;. I set the status to &lt;code&gt;in_progress&lt;/code&gt; immediately. If a second request arrives while the job is running, it gets rejected cleanly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Solid Queue for Background Processing
&lt;/h2&gt;

&lt;p&gt;I integrated &lt;a href="https://github.com/rails/solid_queue" rel="noopener noreferrer"&gt;Solid Queue&lt;/a&gt; — Rails' built-in Active Job backend — to handle dream analysis asynchronously. Making an AI API call synchronously in a controller request? That's a terrible user experience.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SleepAnalyseJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:sleep_analysis&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sleep_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;sleep_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analysis_status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'done'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;

    &lt;span class="no"&gt;SleepAnalysisService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Error analyzing sleep with ID: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sleep_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;presence&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_analysis_not_started&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three layers of defense in this job:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;find_by&lt;/code&gt; instead of &lt;code&gt;find&lt;/code&gt;&lt;/strong&gt;. If the sleep was deleted between the controller and the job execution, I get &lt;code&gt;nil&lt;/code&gt; instead of a crash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Double-check idempotency&lt;/strong&gt;. Race conditions are real. The controller set the status to &lt;code&gt;in_progress&lt;/code&gt;, but I verify again before calling the service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful recovery&lt;/strong&gt;. If something explodes — API down, network issue, whatever — I reset the status to &lt;code&gt;not_started&lt;/code&gt; so the user can retry. Then I re-raise so the job framework handles its retry logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I also put it on a dedicated &lt;code&gt;:sleep_analysis&lt;/code&gt; queue. If my app grows and I have other background work, I can control concurrency and priority per queue. Check out the &lt;a href="https://guides.rubyonrails.org/active_job_basics.html" rel="noopener noreferrer"&gt;Solid Queue documentation&lt;/a&gt; for configuration details.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Service: My Conversation with Mistral
&lt;/h2&gt;

&lt;p&gt;This is the part I was most excited about — and the most nervous about. I decided to use &lt;code&gt;Net::HTTP&lt;/code&gt; from Ruby's stdlib instead of pulling in another gem. For a single API integration, I didn't want the dependency overhead of Faraday or HTTParty.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SleepAnalysisService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="vi"&gt;@mistral_api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MISTRAL_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@mistral_api_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://api.mistral.ai/v1/chat/completions'&lt;/span&gt;
    &lt;span class="vi"&gt;@sleep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sleep&lt;/span&gt;
    &lt;span class="vi"&gt;@locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;send_request_to_mistral&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
    &lt;span class="n"&gt;update_sleep_with_analysis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple entry point: send, parse, persist. Each step checks the previous one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Mistral and Which Model
&lt;/h3&gt;

&lt;p&gt;I chose Mistral's &lt;code&gt;magistral-small-2509&lt;/code&gt; model. It's fast, cost-effective, and produces quality responses for creative and interpretive tasks. The payload follows their &lt;a href="https://docs.mistral.ai/api" rel="noopener noreferrer"&gt;chat completions API&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_payload&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="s1"&gt;'magistral-small-2509'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;response_format: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'text'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;messages: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;role: &lt;/span&gt;&lt;span class="s1"&gt;'system'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;role: &lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="n"&gt;user_prompt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Discover the full &lt;a href="https://docs.mistral.ai/api/" rel="noopener noreferrer"&gt;Mistral API documentation&lt;/a&gt; for endpoints, authentication, and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Hardest Part: Prompt Engineering
&lt;/h3&gt;

&lt;p&gt;I'll be honest — I rewrote the system prompt at least five times. The first version produced responses that were too generic. The second was too clinical. I had to find a balance: insightful without being mystical, personal without being presumptuous.&lt;/p&gt;

&lt;p&gt;Here's what I ended up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;system_prompt&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;PROMPT&lt;/span&gt;&lt;span class="sh"&gt;
    You are an expert dream analyst combining psychology, symbolism, and emotional intelligence.
    Your role is to provide structured, insightful dream interpretations that feel personal and meaningful.

    When analyzing a dream, you must:
    - Identify the core narrative and dominant themes
    - Decode symbols and archetypes present in the dream
    - Connect the emotional tone (mood + intensity) to the dream's meaning
    - Consider the sleep type (lucid, nightmare, recurring, etc.) as interpretive context
    - Use the tags as thematic anchors to deepen the analysis
    - Reference the timing ("when") only if it adds contextual relevance

    Structure your response EXACTLY (adapt title to the language
    corresponding to this locale: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;) as follows:

    ##### 🌙 Global interpretation
    A 3-4 sentence synthesis of the dream's overall meaning.

    ##### 🎭 Theme and meaning
    Identify 2-3 key symbols or themes and explain their psychological significance.

    ##### 💭 Emotional dimension
    Analyze how the mood and intensity level shape the dream's message.

    ##### 🔮 Points to consider
    2-3 open-ended questions or reflections.

    Keep the tone warm, insightful, and grounded.
    Respond in the language corresponding to this locale: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;.
&lt;/span&gt;&lt;span class="no"&gt;  PROMPT&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I learned from this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Structure is everything&lt;/strong&gt;. By specifying exact markdown headers, my frontend can parse and display sections consistently. Without structure, the response was a wall of text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locale in the prompt&lt;/strong&gt;. I interpolate the locale directly into the system prompt. Mistral handles multilingual output beautifully — I just have to tell it which language.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tone guardrails&lt;/strong&gt;. The line "avoid being overly mystical or clinical" was added after I got some really weird responses. Sometimes the AI goes full fortune teller. Sometimes it reads like a medical report. This constraint keeps it in the sweet spot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The user prompt is simpler — I just assemble all the sleep data into a coherent request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_prompt&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;PROMPT&lt;/span&gt;&lt;span class="sh"&gt;
    Please analyze this dream and provide a structured interpretation:

    **Title:** &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
    **Type:** &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
    **When:** &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;happened&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;

    **Description:**
    &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;

    **Tags:** &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
    **Emotional state (current mood):** &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_mood&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
    **Dream intensity:** &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intensity&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;

    Use all of the above attributes in your analysis. The tags, mood,
    and intensity are especially important.
&lt;/span&gt;&lt;span class="no"&gt;  PROMPT&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I make sure to pass every attribute — the title, type, when it happened, the full description, the tags, the mood, and the intensity. I specifically call out that tags, mood, and intensity are important because the AI has a tendency to focus only on the description and ignore the metadata.&lt;/p&gt;

&lt;h3&gt;
  
  
  The HTTP Request
&lt;/h3&gt;

&lt;p&gt;Nothing fancy here — just &lt;code&gt;Net::HTTP&lt;/code&gt; with proper error handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_request_to_mistral&lt;/span&gt;
  &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mistral_api_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_ssl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'Authorization'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Bearer &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mistral_api_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build_payload&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTPSuccess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Failed to get response from Mistral API: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_analysis_not_started&lt;/span&gt;
    &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Error while sending request to Mistral API: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_analysis_not_started&lt;/span&gt;
  &lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the &lt;a href="https://github.com/ruby/net-http" rel="noopener noreferrer"&gt;Ruby Net::HTTP documentation&lt;/a&gt; for more on HTTP requests in Ruby.&lt;/p&gt;

&lt;p&gt;If anything goes wrong — HTTP error, network failure, whatever — I log it, reset the status, and return &lt;code&gt;nil&lt;/code&gt;. The caller handles the nil gracefully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parsing the Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'choices'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'text'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
  &lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll be honest — this parsing is fragile. The path to the text content depends on Mistral's response structure. If they change their API, this breaks. In a production system at scale, I'd use &lt;code&gt;dig&lt;/code&gt; or a response schema validator. For now, it works, and I know exactly where to look if it breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing: Because I Don't Want Surprise API Bills
&lt;/h2&gt;

&lt;p&gt;Testing AI integrations is tricky. I don't want to hit the real Mistral API on every test run — that costs money, it's slow, and it's flaky. But I need confidence that my code handles real responses correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  VCR to the Rescue
&lt;/h3&gt;

&lt;p&gt;I set up &lt;a href="https://github.com/vcr/vcr" rel="noopener noreferrer"&gt;VCR&lt;/a&gt; to record real API responses and replay them deterministically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/support/vcr.rb&lt;/span&gt;
&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cassette_library_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'spec/fixtures/vcr_cassettes'&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hook_into&lt;/span&gt; &lt;span class="ss"&gt;:webmock&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure_rspec_metadata!&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_sensitive_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;MISTRAL_API_KEY&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'MISTRAL_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;filter_sensitive_data&lt;/code&gt; line is crucial — it replaces my actual API key in the cassette files so I never accidentally commit it. Combined with &lt;a href="https://github.com/bblimke/webmock" rel="noopener noreferrer"&gt;WebMock&lt;/a&gt;, VCR intercepts all HTTP requests during tests.&lt;/p&gt;

&lt;p&gt;Then in my service spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;SleepAnalysisService&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:sleep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:sleep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:with_tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'en'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'#call'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'analyzes the sleep and persists the result'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_cassette&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sleep_analysis_service/success'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_present&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analysis_status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'done'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time I run this, VCR hits the real Mistral API and records the response. Every time after that, it replays the cassette. Fast, deterministic, free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing the Job
&lt;/h3&gt;

&lt;p&gt;The job spec verifies idempotency and error recovery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;SleepAnalyseJob&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:sleep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:sleep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'enqueues the job'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_enqueued_job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sleep_analysis'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'does not analyze if already done'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_analysis_done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'existing analysis'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SleepAnalysisService&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;not_to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:call&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing the Endpoint
&lt;/h3&gt;

&lt;p&gt;And at the request level, I verify the idempotency guard actually works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'POST /api/v1/sleeps/:id/analyse'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s1"&gt;'when analysis is already done'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_analysis_done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'analysis text'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'returns ok with appropriate message'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="n"&gt;analyse_api_v1_sleep_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_http_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'code'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'analysis_already_in_progress_or_done'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I Learned Along the Way
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prompt Engineering Is Iterative
&lt;/h3&gt;

&lt;p&gt;I can't stress this enough. The system prompt went through at least five revisions. Each one got better, but I had to actually read the AI outputs and notice patterns: too vague, too clinical, ignoring the tags, wrong language. Only by iterating on real outputs did I get something that felt right.&lt;/p&gt;

&lt;h3&gt;
  
  
  Status Machines Save You From Yourself
&lt;/h3&gt;

&lt;p&gt;The three-state enum combined with double-checks at both the controller and job level — that saved me from bugs I didn't even know I was going to have. Race conditions, retries, duplicate requests — they all get handled gracefully because I thought about failure modes upfront.&lt;/p&gt;

&lt;h3&gt;
  
  
  VCR Is Worth the Setup Time
&lt;/h3&gt;

&lt;p&gt;Recording API responses once and replaying them forever is a superpower. My test suite runs fast, I never worry about API rate limits, and I'm not burning through credits on every CI run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Always Reset on Failure
&lt;/h3&gt;

&lt;p&gt;Early in development, I had a bug where a failed API call left the status stuck at &lt;code&gt;in_progress&lt;/code&gt;. The user couldn't retry. The analysis was orphaned. That's when I added the &lt;code&gt;mark_as_analysis_not_started&lt;/code&gt; calls in every error path. Defensive coding isn't paranoia — it's experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Less Is More for Dependencies
&lt;/h3&gt;

&lt;p&gt;I could have added Faraday or HTTParty or a dedicated Mistral SDK. Instead, I used &lt;code&gt;Net::HTTP&lt;/code&gt; and it works fine. The code is a bit more verbose, but I have one fewer gem to maintain and one fewer dependency to audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building this feature was a great exercise in wrapping something inherently unpredictable — an AI API call — in predictable, well-tested infrastructure. The Mistral integration itself is maybe 30% of the work. The other 70% is status tracking, background jobs, error recovery, idempotency, and testing.&lt;/p&gt;

&lt;p&gt;That's the part I'm most proud of: not that I called an AI API, but that I built a system around it that's resilient enough to run in production without me babysitting it.&lt;/p&gt;

&lt;p&gt;If you're building something similar, my advice is: spend more time on the plumbing than on the prompt. A good prompt with bad error handling will fail silently. A mediocre prompt with great error handling will at least fail gracefully — and you'll have the logs to fix the prompt later.&lt;/p&gt;

&lt;p&gt;You can check out the full implementation in the &lt;a href="https://github.com/fralps/pastel-api/pull/121" rel="noopener noreferrer"&gt;Pull Request on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>mistral</category>
      <category>ai</category>
    </item>
    <item>
      <title>Introducing Squidly: Simplify Your GitHub Workflow Management</title>
      <dc:creator>François</dc:creator>
      <pubDate>Fri, 24 Jan 2025 12:57:22 +0000</pubDate>
      <link>https://dev.to/fralps/introducing-squidly-simplify-your-github-workflow-management-b5h</link>
      <guid>https://dev.to/fralps/introducing-squidly-simplify-your-github-workflow-management-b5h</guid>
      <description>&lt;p&gt;Hello, Dev.to community! 🚀&lt;/p&gt;

&lt;p&gt;I'm excited to introduce you to &lt;strong&gt;&lt;a href="https://beta.squidly.sh" rel="noopener noreferrer"&gt;Squidly (beta)&lt;/a&gt;&lt;/strong&gt;, a SaaS platform designed to streamline your GitHub workflow management. Whether you're a solo developer or part of a larger team, Squidly offers a comprehensive suite of features to help you manage, monitor, and gain insights into your GitHub workflows—all in one place.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;🚧 Note: the platform is in Beta stage, server can make from 30 seconds to 1 minute to be up and ready.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Squidly?
&lt;/h2&gt;

&lt;p&gt;Managing multiple GitHub workflows can be challenging, especially when you're juggling various projects. Squidly aims to simplify this process by providing a centralized dashboard where you can visualize and control your workflows efficiently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dashboard Overview&lt;/strong&gt;: Get a quick overview of all your GitHub workflows. Monitor their status, view statistics, and manage them with ease.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxf5gts9b62irgqibxskj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxf5gts9b62irgqibxskj.png" alt="Dashboard preview" width="800" height="628"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Repository Management&lt;/strong&gt;: Add or remove repositories directly from Squidly. Gain insights into your workflows and manage them without leaving the platform.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa61nbednba3hd66s4jwe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa61nbednba3hd66s4jwe.png" alt="Repositories preview" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Workflow Control&lt;/strong&gt;: View all your workflows and their statuses. Re-run or cancel workflows directly from the list.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3o2rn69p84p85gv2mm8a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3o2rn69p84p85gv2mm8a.png" alt="Workflows preview" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Billing Usage Recap&lt;/strong&gt;: For premium users, Squidly provides a detailed billing usage recap, helping you keep track of your workflow usage.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;More details is on the pricing section: &lt;a href="https://beta.squidly.sh" rel="noopener noreferrer"&gt;Squidly&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing Plans
&lt;/h3&gt;

&lt;p&gt;Squidly is currently in beta and free to use. We offer a &lt;strong&gt;Free Plan&lt;/strong&gt; for small projects and a &lt;strong&gt;Premium Plan&lt;/strong&gt; for developers who need more advanced features. A &lt;strong&gt;Teams Plan&lt;/strong&gt; is also in the works for enterprise users.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Test
&lt;/h3&gt;

&lt;p&gt;To test Squidly, navigate to the relevant sections of the app and explore the features. Our intuitive interface makes it easy to get up and running quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Join Our Community
&lt;/h3&gt;

&lt;p&gt;We'd love for you to join our growing community of developers. Follow us on &lt;a href="https://twitter.com/fralps_dev" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; for updates, or reach out through our contact form if you have any questions.&lt;/p&gt;

&lt;p&gt;Thank you for taking the time to learn about Squidly. We hope it becomes an invaluable tool in your development workflow.&lt;/p&gt;

&lt;p&gt;You can try the tool directly in Beta environment (All data will be deleted at the end of Beta testing).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://beta.squidly.sh" rel="noopener noreferrer"&gt;Squidly - All your Github workflows in one place&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy coding! 🎉&lt;/p&gt;

</description>
      <category>saas</category>
      <category>githubactions</category>
      <category>svelte</category>
      <category>adonisjs</category>
    </item>
    <item>
      <title>Question - Tools to see differences between JS frameworks</title>
      <dc:creator>François</dc:creator>
      <pubDate>Thu, 30 Mar 2023 14:01:55 +0000</pubDate>
      <link>https://dev.to/fralps/question-tools-to-see-differences-between-js-frameworks-4nja</link>
      <guid>https://dev.to/fralps/question-tools-to-see-differences-between-js-frameworks-4nja</guid>
      <description>&lt;p&gt;Hi developers! 👋&lt;/p&gt;

&lt;p&gt;Do you have any tools to see the difference in terms of code between different frontend frameworks?&lt;br&gt;
For example, difference to declare a props, events, ...&lt;/p&gt;

&lt;p&gt;Thanks a lot 🙏&lt;/p&gt;

</description>
    </item>
    <item>
      <title>ReactJS vs Vue.js - Feedback about two long-term projects</title>
      <dc:creator>François</dc:creator>
      <pubDate>Wed, 30 Mar 2022 13:33:55 +0000</pubDate>
      <link>https://dev.to/fralps/reactjs-vs-vuejs-feedback-about-two-long-term-projects-4fpi</link>
      <guid>https://dev.to/fralps/reactjs-vs-vuejs-feedback-about-two-long-term-projects-4fpi</guid>
      <description>&lt;p&gt;I don't think it is really useful to introduce these two frameworks/libraries since you probably already know them. If it is not the case, you can find a quick introduction on those links &lt;a href="https://fr.reactjs.org/" rel="noopener noreferrer"&gt;ReactJS&lt;/a&gt;/&lt;a href="https://vuejs.org/" rel="noopener noreferrer"&gt;Vue.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is a graph of their evolution in terms of Github stars from 2014 to today 📊.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggkgfndgbkbg0s7tpqqj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggkgfndgbkbg0s7tpqqj.png" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;Number of stars on GitHub projects React and Vue (source: &lt;a href="https://star-history.t9t.io/#facebook/react&amp;amp;vuejs/vue&amp;amp;angular" rel="noopener noreferrer"&gt;https://star-history.t9t.io/#facebook/react&amp;amp;amp;vuejs/vue&amp;amp;amp;angular&lt;/a&gt;)
  &lt;p&gt;&lt;/p&gt;




&lt;p&gt;As a comparison, the most starred repository on GitHub is &lt;a href="https://www.freecodecamp.org/" rel="noopener noreferrer"&gt;FreeCodeCamp&lt;/a&gt;, with over &lt;strong&gt;340k&lt;/strong&gt; stars 🌟&amp;nbsp;. It is an open source project with a large codebase and a friendly community where you can learn to code for free with over 8,000 tutorials.&lt;/p&gt;

&lt;p&gt;In this article, I will give you my feedback on the use of these 2 frameworks through two projects at &lt;a href="https://kinoba.fr" rel="noopener noreferrer"&gt;Kinoba&lt;/a&gt; 💛&lt;/p&gt;

&lt;h4&gt;
  
  
  DISCLAIMER
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;The projects I will be talking about started in 2020 and are part of my discovery and learning of main frontend frameworks. I deliberately did not go into advanced technical details to make it accessible to beginners, tech lovers and also experienced developers. Today, I'm working with these two amazing frameworks in my daily life 🙌&amp;nbsp;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Vue.js
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;First steps in front-end frameworks with&amp;nbsp;Vue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My first Vue.js project at Kinoba was made with the &lt;a href="https://highday.fr" rel="noopener noreferrer"&gt;Highday&lt;/a&gt; project. This platform allows you to describe your professional life in synthetic, graphic and contemporary formats, to share with your community.&lt;/p&gt;

&lt;p&gt;The technical stack used is Vue for the front-end and &lt;a href="https://rubyonrails.org/" rel="noopener noreferrer"&gt;Ruby On Rails&lt;/a&gt; for the back-end.&lt;/p&gt;

&lt;p&gt;I think that these 2 technologies work really well together: development is super smooth, Vue is intuitive and the learning curve is awesome.&lt;/p&gt;

&lt;p&gt;The choice of the framework for this project was mainly made thanks to the growth of Vue, its simplicity of understanding, its lightness compared to others and its growing community. Others criteria came into consideration, such as the possibility to install the app in PWA mode on mobile or desktop devices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I appreciated during the development&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚙️ The principle of the "single file component" is useful: it allows you to keep all the code related to a component in one place, so it is easier to organize your project. And you don't end up with styles from one component overwriting the styles of another component.&lt;/li&gt;
&lt;li&gt;📚 The documentation is great&lt;/li&gt;
&lt;li&gt;🤩 Quick and easy PWA setup&lt;/li&gt;
&lt;li&gt;🏢 Vue was not created by a big company like Facebook or Google.&lt;/li&gt;
&lt;li&gt;📦 Packages are awesome!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Examples of packages&amp;nbsp;used&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/axios/axios" rel="noopener noreferrer"&gt;axios&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/SeregPie/VueWordCloud" rel="noopener noreferrer"&gt;vuewordcloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/filrak/vue-offline" rel="noopener noreferrer"&gt;vue-offline&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Agontuk/vue-cropperjs" rel="noopener noreferrer"&gt;vue-cropperjs&lt;/a&gt; paired with &lt;a href="https://github.com/fengyuanchen/compressorjs" rel="noopener noreferrer"&gt;compressorjs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/apertureless/vue-cookie-law" rel="noopener noreferrer"&gt;vue-cookie-law&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While working on the project, the latest version of Vue (Vue 3) had just been released (&lt;a href="https://twitter.com/youyuxi" rel="noopener noreferrer"&gt;Evan You&lt;/a&gt; 💚️), with lots of new features. I am already looking forward to start a new project with this new version. 🤩&lt;/p&gt;




&lt;h3&gt;
  
  
  React
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;First steps with&amp;nbsp;React&lt;/strong&gt;&lt;br&gt;
The second framework I learned is &lt;a href="https://fr.reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt;, thanks to an event platform.&lt;/p&gt;

&lt;p&gt;Unlike the project with Vue, this one required the use of React because the project integrated massive rendering and performance issues. We needed a more complete and robust framework and React turned out to be a perfect fit for this type of project.&lt;/p&gt;

&lt;p&gt;We also implemented a custom chat made with &lt;a href="https://www.npmjs.com/package/actioncable" rel="noopener noreferrer"&gt;actioncable&lt;/a&gt; and a custom backoffice for event administrators.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I appreciated during the development&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🤓 Learning a "new language", JSX.&lt;/li&gt;
&lt;li&gt;⚙️ We used the "Class Component" system (I have not used the "Functionnal Component" yet so I have no hindsight on this point)&lt;/li&gt;
&lt;li&gt;📣 The massive React community&lt;/li&gt;
&lt;li&gt;💪 The stability of the library&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Examples of packages&amp;nbsp;used&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/actioncable" rel="noopener noreferrer"&gt;actioncable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react-redux.js.org/" rel="noopener noreferrer"&gt;react-redux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/react-cropper/react-cropper" rel="noopener noreferrer"&gt;react-cropper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/zenoamaro/react-quill" rel="noopener noreferrer"&gt;react-quill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/twobin/react-lazyload" rel="noopener noreferrer"&gt;react-lazyload&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next time, I hope to be able to test how Functionnal Component works to see the different aspects of using React. I have also heard a lot about &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; or &lt;a href="https://reactnative.dev/" rel="noopener noreferrer"&gt;React Native&lt;/a&gt; from several friends, they sound pretty cool 😎&lt;/p&gt;


&lt;h3&gt;
  
  
  Simple code examples between Vue.js and&amp;nbsp;React
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Loops&lt;/strong&gt;&lt;/p&gt;


  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkny8q29j2sv2l86fxnjl.png" width="800" height="545"&gt;Loop in React
  


&lt;p&gt;&lt;br&gt;
  &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j9qfle51l4jbyzthjkt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j9qfle51l4jbyzthjkt.png" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;Loop in Vue.js
  &lt;br&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;I find it so much easier in Vue.&lt;/p&gt;

&lt;p&gt;Reading the code becomes quite complicated when you have multiple loops in React. The syntax is too heavy in my opinion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conditions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypaanzg2w7g9zgdrdhjf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypaanzg2w7g9zgdrdhjf.png" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;Condition in React
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58ixhuyumeviv87azxxh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58ixhuyumeviv87azxxh.png" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;Condition in Vue.js
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;Same thing for the conditions, I find React's JSX pretty heavy and if there are too many conditions so it quickly becomes unmaintainable over time.&lt;/p&gt;




&lt;h3&gt;
  
  
  To conclude…
&lt;/h3&gt;

&lt;p&gt;The learning of the two frameworks was done without too many worries but which one do you think I prefer? 😉&lt;/p&gt;

&lt;p&gt;Because of my knowledge of Vue, I did not have too many learning issues with React, and for any problem I had my colleagues as a backup 💪. This is a personal opinion but I prefer to use Vue.js for all the points I mentioned above.&lt;/p&gt;

&lt;p&gt;JSX complicates a little bit the readability of the code but it is useful not to have an html, css and a separate javascript file.&lt;/p&gt;

&lt;p&gt;Having a separate stylesheet is not so annoying, we still manage to organize ourselves even if some styles may overwrite styles of other components.&lt;/p&gt;

&lt;p&gt;In my future personal projects, I would also like to learn the &lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Svelte.js&lt;/a&gt; framework which, as its name suggests, greatly reduces the weight of the application once compiled. Indeed, the virtual DOM is not present, which is one less thing to load 🚀. It promises better loading speed compared to other frameworks and as web developers we are always looking for better performance. So, I think Svelte can be interesting in that regard and its popularity keeps increasing.&lt;/p&gt;

&lt;p&gt;👋 See you soon &lt;a href="https://kinoba.fr/en" rel="noopener noreferrer"&gt;@Kinoba&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>vue</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Se reconvertir dans le développement web 👩‍💻👨‍💻</title>
      <dc:creator>François</dc:creator>
      <pubDate>Fri, 04 Feb 2022 09:54:14 +0000</pubDate>
      <link>https://dev.to/fralps/se-reconvertir-dans-le-developpement-web-3kmb</link>
      <guid>https://dev.to/fralps/se-reconvertir-dans-le-developpement-web-3kmb</guid>
      <description>&lt;p&gt;Viens découvrir les premiers articles de notre série d'interviews au format court sur la reconversion dans le développement web.&lt;/p&gt;

&lt;p&gt;Chez &lt;a href="https://kinoba.fr" rel="noopener noreferrer"&gt;Kinoba&lt;/a&gt;, nous avons des profils très différents. Six membres de l’équipe se sont reconvertis dans le développement web après s’être essayés à des métiers divers et variés. Ils témoignent de leur reconversion dans cette série d’interviews au format court :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://kinoba.medium.com/se-reconvertir-dans-le-d%C3%A9veloppement-web-t%C3%A9moignages-de-la-team-kinoba-1-5-1fce0b91f8e4" rel="noopener noreferrer"&gt;Épisode 1 - Sharon&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://kinoba.medium.com/se-reconvertir-dans-le-d%C3%A9veloppement-web-t%C3%A9moignages-de-la-team-kinoba-2-6-f3a7543a748f" rel="noopener noreferrer"&gt;Épisode 2 - François&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kinoba.medium.com/se-reconvertir-dans-le-d%C3%A9veloppement-web-t%C3%A9moignages-de-la-team-kinoba-3-6-8c493742e37b" rel="noopener noreferrer"&gt;Épisode 3 - Émilie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@kinoba/se-reconvertir-dans-le-d%C3%A9veloppement-web-t%C3%A9moignages-de-la-team-kinoba-4-6-c16be200ec77" rel="noopener noreferrer"&gt;Épisode 4 - Émilie (une deuxième Émilie)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@kinoba/se-reconvertir-dans-le-d%C3%A9veloppement-web-t%C3%A9moignages-de-la-team-kinoba-5-6-43af34bb77a5" rel="noopener noreferrer"&gt;Épisode 5 - Mickaël&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@kinoba/se-reconvertir-dans-le-d%C3%A9veloppement-web-t%C3%A9moignages-de-la-team-kinoba-6-6-ccb3810b875f" rel="noopener noreferrer"&gt;Épisode 6 - Clément&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Et toi, comment as-tu vécu ta reconversion ? ⬇️ ⬇️ ⬇️&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>codenewbie</category>
      <category>french</category>
    </item>
    <item>
      <title>3 ways to send emails with only few lines of code and Gmail - Python - Part 3</title>
      <dc:creator>François</dc:creator>
      <pubDate>Mon, 28 Jun 2021 14:01:51 +0000</pubDate>
      <link>https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-python-part-3-13e0</link>
      <guid>https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-python-part-3-13e0</guid>
      <description>&lt;p&gt;&lt;em&gt;We will see how to send a simple email with the help of three different programming languages: Javascript, Ruby and Python&lt;br&gt;
Before you start you need to create a Gmail account.&lt;br&gt;
Do not forget to accept and allow the "Less secure apps" access in order use your scripts with your Gmail smtp connection.&lt;br&gt;
I'll let you do this on your own, you don't need a tutorial for this&lt;/em&gt; 😜 &lt;/p&gt;
&lt;h2&gt;
  
  
  Python 🐍
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;We are going to use the &lt;a href="https://docs.python.org/3/library/smtplib.html" rel="noopener noreferrer"&gt;Smtplib&lt;/a&gt; and the &lt;a href="https://docs.python.org/3/library/email.mime.html" rel="noopener noreferrer"&gt;MIMEMultipart&lt;/a&gt; libraries:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;smtplib&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;email.mime.multipart&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEMultipart&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;email.mime.text&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEText&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Define Gmail account info and recipient email:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The email addresses and password
&lt;/span&gt;&lt;span class="n"&gt;sender_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;youremail@gmail.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;sender_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;yourpassword&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;gmail_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;587&lt;/span&gt;

&lt;span class="n"&gt;receiver_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;myfriend@yopmail.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;mail_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Easy peazy lemon squeezy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Setup the MIME:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MIMEMultipart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;From&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender_address&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;To&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;receiver_address&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Subject&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sending email using Python&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MIMEText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;plain&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Create SMTP session for sending the mail:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;smtplib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SMTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;smtp.gmail.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gmail_port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;starttls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# enable security
&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender_password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;receiver_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Email Sent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here the final code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;smtplib&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;email.mime.multipart&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEMultipart&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;email.mime.text&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEText&lt;/span&gt;

&lt;span class="c1"&gt;# The email addresses and password
&lt;/span&gt;&lt;span class="n"&gt;sender_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;youremail@gmail.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;sender_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;yourpassword&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;gmail_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;587&lt;/span&gt;

&lt;span class="n"&gt;receiver_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;myfriend@yopmail.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;mail_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Easy peezy lemon squeezy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;# Setup the MIME
&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MIMEMultipart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;From&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender_address&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;To&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;receiver_address&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Subject&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sending email using Python&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;# The body and the attachments for the mail
&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MIMEText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;plain&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Create SMTP session for sending the mail
&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;smtplib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SMTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;smtp.gmail.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gmail_port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;starttls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# enable security
&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender_password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;receiver_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Email Sent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The power of Python 🐍
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbsc8k8fmthjxh5ls12v5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbsc8k8fmthjxh5ls12v5.gif" alt="Python beast" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;With those scripts, we went through the basics of the module, gem and library (read the docs 📚) but you can also do amazing things with them like loops, sending attachments, automated things... feel free to do want you want.&lt;/p&gt;

&lt;p&gt;Thanks for reading this tiny scripts serie 😊 &lt;br&gt;
See you soon &lt;a href="https://kinoba.fr" rel="noopener noreferrer"&gt;@Kinoba&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-javascript-part-1-4i92"&gt;Javascript 🚀 - Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-ruby-part-2-23nc"&gt;Ruby 💎 - Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Python 🐍 - Part 3&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>smtp</category>
      <category>email</category>
      <category>library</category>
    </item>
    <item>
      <title>3 ways to send emails with only few lines of code and Gmail - Ruby - Part 2</title>
      <dc:creator>François</dc:creator>
      <pubDate>Mon, 21 Jun 2021 11:48:01 +0000</pubDate>
      <link>https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-ruby-part-2-23nc</link>
      <guid>https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-ruby-part-2-23nc</guid>
      <description>&lt;p&gt;&lt;em&gt;We will see how to send a simple email with the help of three different programming languages: Javascript, Ruby and Python&lt;br&gt;
Before you start you need to create a Gmail account.&lt;br&gt;
Do not forget to accept and allow the "Less secure apps" access in order use your scripts with your Gmail smtp connection.&lt;br&gt;
I'll let you do this on your own, you don't need a tutorial for this&lt;/em&gt; 😜&lt;/p&gt;
&lt;h2&gt;
  
  
  Ruby 💎
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;We are going to use the &lt;a href="https://github.com/mikel/mail" rel="noopener noreferrer"&gt;Mail&lt;/a&gt; gem:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem install mail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Require it at the beginning of your &lt;code&gt;send_email.rb&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'mail'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Declare Gmail account info:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gmail account info&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;address: &lt;/span&gt;&lt;span class="s1"&gt;'smtp.gmail.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;port: &lt;/span&gt;&lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# gmail smtp port number&lt;/span&gt;
  &lt;span class="ss"&gt;domain: &lt;/span&gt;&lt;span class="s1"&gt;'gmail.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;user_name: &lt;/span&gt;&lt;span class="s1"&gt;'youremail@gmail.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;password: &lt;/span&gt;&lt;span class="s1"&gt;'yourpassword'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;authentication: &lt;/span&gt;&lt;span class="s1"&gt;'plain'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Initialize Mail &lt;code&gt;delivery_method&lt;/code&gt; info:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defaults&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;delivery_method&lt;/span&gt; &lt;span class="ss"&gt;:smtp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Send email 📧 :
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deliver&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s1"&gt;'myfriend@yopmail.com'&lt;/span&gt;
  &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'youremail@gmail.com'&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="s1"&gt;'Sending email using Ruby'&lt;/span&gt;
  &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="s1"&gt;'Easy peasy lemon squeezy'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here the final code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'mail'&lt;/span&gt;

&lt;span class="c1"&gt;# Gmail account info&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;address: &lt;/span&gt;&lt;span class="s1"&gt;'smtp.gmail.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;port: &lt;/span&gt;&lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;domain: &lt;/span&gt;&lt;span class="s1"&gt;'gmail.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;user_name: &lt;/span&gt;&lt;span class="s1"&gt;'youremail@gmail.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;password: &lt;/span&gt;&lt;span class="s1"&gt;'yourpassword'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;authentication: &lt;/span&gt;&lt;span class="s1"&gt;'plain'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="no"&gt;Mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defaults&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;delivery_method&lt;/span&gt; &lt;span class="ss"&gt;:smtp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deliver&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s1"&gt;'myfriend@yopmail.com'&lt;/span&gt;
  &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'youremail@gmail.com'&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="s1"&gt;'Sending email using Ruby'&lt;/span&gt;
  &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="s1"&gt;'Easy peasy lemon squeezy'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Email sent'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The ease of Ruby 💎
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fly7ujqdyu4lc568krrcv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fly7ujqdyu4lc568krrcv.gif" alt="Easy Ruby" width="498" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-javascript-part-1-4i92"&gt;Javascript 🚀 - Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Ruby 💎 - Part 2&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-python-part-3-13e0"&gt;Python 🐍 - Part 3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>smtp</category>
      <category>email</category>
      <category>gem</category>
    </item>
    <item>
      <title>3 ways to send emails with only few lines of code and Gmail - Javascript - Part 1</title>
      <dc:creator>François</dc:creator>
      <pubDate>Mon, 14 Jun 2021 10:05:45 +0000</pubDate>
      <link>https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-javascript-part-1-4i92</link>
      <guid>https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-javascript-part-1-4i92</guid>
      <description>&lt;p&gt;&lt;em&gt;We will see how to send a simple email with the help of three different programming languages: Javascript, Ruby and Python&lt;br&gt;
Before you start you need to create a Gmail account.&lt;br&gt;
Do not forget to accept and allow the "Less secure apps" access in order use your scripts with your Gmail smtp connection.&lt;br&gt;
I'll let you do this on your own, you don't need a tutorial for this&lt;/em&gt; 😜&lt;/p&gt;
&lt;h2&gt;
  
  
  Javascript 🚀
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;For the first script, we are going to use the &lt;a href="https://nodemailer.com/about/" rel="noopener noreferrer"&gt;Nodemailer&lt;/a&gt; module:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add nodemailer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Require or import the module into your &lt;code&gt;index.js&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodemailer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Initialize the mailer with our Gmail account info:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Gmail account info&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gmail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youremail@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yourpassword&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Create your email:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Email info&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mailOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youremail@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myfriend@yopmail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sending email using Node.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Easy peasy lemon squeezy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Sending your email:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Send email and retrieve server response&lt;/span&gt;
&lt;span class="nx"&gt;transporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mailOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email sent: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here the final code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodemailer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Gmail account info&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gmail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youremail@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yourpassword&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Email info&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mailOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youremail@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myfriend@yopmail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sending email using Node.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Easy peasy lemon squeezy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Send email 📧  and retrieve server response&lt;/span&gt;
&lt;span class="nx"&gt;transporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mailOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email sent: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Javascript buddy 🤝
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxii073i2ucq8cwcltgm.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxii073i2ucq8cwcltgm.gif" alt="Javascript buddy" width="480" height="270"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Javascript 🚀 - Part 1&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-ruby-part-2-23nc"&gt;Ruby 💎 - Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-python-part-3-13e0"&gt;Python 🐍 - Part 3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>smtp</category>
      <category>email</category>
      <category>nodemailer</category>
    </item>
    <item>
      <title>3 ways to send emails with only few lines of code and Gmail - Series</title>
      <dc:creator>François</dc:creator>
      <pubDate>Fri, 11 Jun 2021 11:56:07 +0000</pubDate>
      <link>https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-series-4lf3</link>
      <guid>https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-series-4lf3</guid>
      <description>&lt;p&gt;&lt;em&gt;We will see how to send a simple email with the help of three different programming languages: Javascript, Ruby and Python&lt;br&gt;
Before you start you need to create a Gmail account.&lt;br&gt;
Do not forget to accept and allow the "Less secure apps" access in order use your scripts with your Gmail smtp connection.&lt;br&gt;
I'll let you do this on your own, you don't need a tutorial for this&lt;/em&gt; 😜  &lt;/p&gt;

&lt;h2&gt;
  
  
  Table of teasing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-javascript-part-1-4i92"&gt;Javascript 🚀 - Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-ruby-part-2-23nc"&gt;Ruby 💎 - Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/fralps/3-ways-to-send-emails-with-only-few-lines-of-code-and-gmail-python-part-3-13e0"&gt;Python 🐍 - Part 3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>ruby</category>
      <category>python</category>
      <category>smtp</category>
    </item>
    <item>
      <title>Configure Scaleway S3 for Active Storage in Rails 6.0</title>
      <dc:creator>François</dc:creator>
      <pubDate>Fri, 26 Mar 2021 08:20:08 +0000</pubDate>
      <link>https://dev.to/fralps/configure-scaleway-s3-for-active-storage-in-rails-6-0-4n3n</link>
      <guid>https://dev.to/fralps/configure-scaleway-s3-for-active-storage-in-rails-6-0-4n3n</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;a href="https://www.scaleway.com/en/" rel="noopener noreferrer"&gt;Scaleway&lt;/a&gt; is a French web hosting company, founded by &lt;a href="https://fr.wikipedia.org/wiki/Xavier_Niel" rel="noopener noreferrer"&gt;Xavier Niel&lt;/a&gt; in 1999. It provides physical and online dedicated servers, domain registration services and datacenters.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In 2020, Scaleway was the second player in France behind OVHCloud and the third in Europe.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you want an alternative to Amazon AWS S3 storage services that we use &lt;a href="https://kinoba.fr" rel="noopener noreferrer"&gt;@Kinoba&lt;/a&gt;, follow along this tiny and easy tutorial&lt;/em&gt; 😊&lt;/p&gt;




&lt;h2&gt;
  
  
  Scaleway part
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Let's begin with the Scaleway config&lt;/em&gt; 👨🏻‍🔧&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1️⃣ &lt;strong&gt;First step:&lt;/strong&gt; Create an account on Scaleway Elements:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉🏻 &lt;a href="https://www.scaleway.com/fr/object-storage/" rel="noopener noreferrer"&gt;https://www.scaleway.com/fr/object-storage/&lt;/a&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2️⃣ &lt;strong&gt;Second step:&lt;/strong&gt; Create your S3 bucket by going to your Scaleway console and "Object Storage" tab.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here, a help procedure if needed:&lt;br&gt;
 👉🏻 &lt;a href="https://www.scaleway.com/en/docs/object-storage-feature/#-Operation-Procedures" rel="noopener noreferrer"&gt;https://www.scaleway.com/en/docs/object-storage-feature/#-Operation-Procedures&lt;/a&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3️⃣ &lt;strong&gt;Third step:&lt;/strong&gt; Generate your Access key and your Secret key 🔐 (do not forget to store them somewhere like 1Password or Bitwarden for example):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉🏻 &lt;a href="https://www.scaleway.com/en/docs/generate-api-keys/" rel="noopener noreferrer"&gt;https://www.scaleway.com/en/docs/generate-api-keys/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Ruby On Rails part
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;The funniest part: integrating Scaleway into your Rails app&lt;/em&gt; 👩🏽‍💻&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After installing ActiveStorage and creating the active_storage_tables migration, you can add the "&lt;strong&gt;aws-sdk-s3&lt;/strong&gt;" gem to your Gemfile
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'aws-sdk-s3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;require: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Then, you can add your credentials to your &lt;strong&gt;.env&lt;/strong&gt; file (do not forget to add your file to your &lt;strong&gt;.gitignore&lt;/strong&gt;)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .env&lt;/span&gt;
&lt;span class="no"&gt;SCALEWAY_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;YOUR_ACCESS_KEY_ID&lt;/span&gt;
&lt;span class="no"&gt;SCALEWAY_SECRET_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;YOUR_SECRET_ACCESS_KEY_ID&lt;/span&gt;
&lt;span class="no"&gt;SCALEWAY_BUCKET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;YOUR_BUCKET_NAME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Add this config to you &lt;strong&gt;storage.yml&lt;/strong&gt; with your personal keys and bucket
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# storage.yml&lt;/span&gt;
&lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Disk&lt;/span&gt;
  &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= Rails.root.join("tmp/storage") %&amp;gt;&lt;/span&gt;

&lt;span class="na"&gt;local&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Disk&lt;/span&gt;
  &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= Rails.root.join("storage") %&amp;gt;&lt;/span&gt;

&lt;span class="na"&gt;scaleway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S3&lt;/span&gt;
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://s3.fr-par.scw.cloud&lt;/span&gt; &lt;span class="c1"&gt;# Scaleway API endpoint, depending on your region&lt;/span&gt;
  &lt;span class="na"&gt;access_key_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV['SCALEWAY_ACCESS_KEY_ID'] %&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# Do not forget to hide your secrets&lt;/span&gt;
  &lt;span class="na"&gt;secret_access_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV['SCALEWAY_SECRET_ACCESS_KEY_ID'] %&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# Do not forget to hide your secrets&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fr-par&lt;/span&gt; &lt;span class="c1"&gt;# Your bucket region, here it's Paris&lt;/span&gt;
  &lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV['SCALEWAY_BUCKET_NAME'] %&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# Your bucket name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Do not forget to tell Rails to use Scaleway storage in &lt;strong&gt;production&lt;/strong&gt; environment
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# production.rb&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active_storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:scaleway&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Finally, you can attach your files to your records as usual 🙌🏻
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# user.rb&lt;/span&gt;
&lt;span class="n"&gt;has_one_attached&lt;/span&gt; &lt;span class="ss"&gt;:avatar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# users_controller.rb&lt;/span&gt;
&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:avatar&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  That's it 🎉
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ap2niukc66p8d7j002u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ap2niukc66p8d7j002u.gif" alt="easy peasy gif" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Check it out on &lt;a href="https://kinoba.medium.com/configure-scaleway-s3-for-active-storage-in-rails-6-0-7502755842ad" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;François Loupias - Fullstack web developer &lt;a href="https://kinoba.fr" rel="noopener noreferrer"&gt;@Kinoba&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>activestorage</category>
      <category>scaleway</category>
      <category>s3</category>
    </item>
  </channel>
</rss>
