<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Форма обратного звонка на React + Express + Яндекс SMTP &#x2F; Отправка форм обратных связей через SMTP]]></title><description><![CDATA[<h2>Схема работы</h2>
<pre><code>React-форма → POST /api/callback → Express → SMTP Яндекса → письмо на почту
</code></pre>
<p dir="auto">React отправляет <code>POST</code>-запрос. Express принимает данные и отправляет письмо через <code>nodemailer</code>. Логин и пароль хранятся только на сервере.</p>
<hr />
<h2>Важно: IMAP ≠ SMTP</h2>
<ul>
<li><strong>IMAP</strong> - для чтения почты.</li>
<li><strong>SMTP</strong> - для отправки писем.</li>
</ul>
<p dir="auto">Включенный IMAP в настройках Яндекса не дает возможности отправлять письма. Нужен <strong>SMTP + пароль приложения</strong>.</p>
<hr />
<h2>Установка</h2>
<pre><code class="language-bash">npm install express nodemailer cors dotenv
</code></pre>
<hr />
<h2>Переменные окружения</h2>
<p dir="auto">Создайте файл <code>.env</code> и добавьте его в <code>.gitignore</code>:</p>
<pre><code>YANDEX_EMAIL=your@yandex.ru
YANDEX_APP_PASSWORD=пароль_приложения
PORT=3001
</code></pre>
<p dir="auto"><strong>Пароль приложения</strong> создаётся в Яндекс ID → Безопасность → Пароли приложений → Почта. Основной пароль от аккаунта использовать нельзя.</p>
<hr />
<h2>Модуль отправки</h2>
<pre><code class="language-js">// lib/mailer.js
import nodemailer from 'nodemailer';

const transporter = nodemailer.createTransport({
  host: 'smtp.yandex.ru',
  port: 465,
  secure: true,
  auth: {
    user: process.env.YANDEX_EMAIL,
    pass: process.env.YANDEX_APP_PASSWORD,
  },
});

export async function sendCallbackEmail({ name, phone, message }) {
  await transporter.sendMail({
    from: `"Сайт" &lt;${process.env.YANDEX_EMAIL}&gt;`,
    to: process.env.YANDEX_EMAIL,
    subject: `Обратный звонок: ${name}`,
    html: `
      &lt;p&gt;&lt;b&gt;Имя:&lt;/b&gt; ${name}&lt;/p&gt;
      &lt;p&gt;&lt;b&gt;Телефон:&lt;/b&gt; ${phone}&lt;/p&gt;
      &lt;p&gt;&lt;b&gt;Сообщение:&lt;/b&gt; ${message || 'не указано'}&lt;/p&gt;
    `,
  });
}
</code></pre>
<hr />
<h2>Express-сервер</h2>
<pre><code class="language-js">// server.js
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import { sendCallbackEmail } from './lib/mailer.js';

const app = express();

app.use(cors({ origin: 'http://localhost:3000' }));
app.use(express.json());

app.post('/api/callback', async (req, res) =&gt; {
  const { name, phone, message } = req.body;

  if (!name || !phone) {
    return res.status(400).json({ error: 'Имя и телефон обязательны' });
  }

  try {
    await sendCallbackEmail({ name, phone, message });
    res.json({ success: true });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Ошибка отправки письма' });
  }
});

app.listen(process.env.PORT, () =&gt; {
  console.log(`Server running on port ${process.env.PORT}`);
});
</code></pre>
<hr />
<h2>React-форма</h2>
<pre><code class="language-jsx">// components/CallbackForm.jsx
import { useState } from 'react';

const INITIAL = { name: '', phone: '', message: '' };

export function CallbackForm() {
  const [form, setForm] = useState(INITIAL);
  const [status, setStatus] = useState('idle'); // idle | loading | success | error

  const onChange = (e) =&gt;
    setForm((p) =&gt; ({ ...p, [e.target.name]: e.target.value }));

  const onSubmit = async (e) =&gt; {
    e.preventDefault();
    setStatus('loading');

    try {
      const res = await fetch('http://localhost:3001/api/callback', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(form),
      });

      if (!res.ok) throw new Error();
      setStatus('success');
      setForm(INITIAL);
    } catch {
      setStatus('error');
    }
  };

  return (
    &lt;form onSubmit={onSubmit}&gt;
      &lt;input name="name"    value={form.name}    onChange={onChange} placeholder="Имя"     required /&gt;
      &lt;input name="phone"   value={form.phone}   onChange={onChange} placeholder="Телефон" required /&gt;
      &lt;textarea name="message" value={form.message} onChange={onChange} placeholder="Сообщение" /&gt;

      &lt;button type="submit" disabled={status === 'loading'}&gt;
        {status === 'loading' ? 'Отправка...' : 'Заказать звонок'}
      &lt;/button&gt;

      {status === 'success' &amp;&amp; &lt;p&gt;✅ Заявка отправлена&lt;/p&gt;}
      {status === 'error'   &amp;&amp; &lt;p&gt;❌ Ошибка. Попробуйте позже&lt;/p&gt;}
    &lt;/form&gt;
  );
}
</code></pre>
<hr />
<h2>Структура проекта</h2>
<pre><code>project/
├── server.js
├── lib/
│   └── mailer.js
├── .env              ← не коммитить
├── .gitignore
└── client/           ← React-приложение
    └── src/
        └── components/
            └── CallbackForm.jsx
</code></pre>
<hr />
<h2>Запуск</h2>
<pre><code class="language-bash"># Сервер
node server.js

# React (отдельный терминал)
npm start
</code></pre>
<hr />
<h2>Частые ошибки</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Ошибка</th>
<th>Причина</th>
<th>Решение</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Invalid login</code></td>
<td>Основной пароль вместо пароля приложения</td>
<td>Создать пароль приложения в Яндекс ID</td>
</tr>
<tr>
<td><code>Connection refused</code></td>
<td>Сервер не запущен или неверный порт</td>
<td>Проверить <code>PORT</code> в <code>.env</code></td>
</tr>
<tr>
<td>CORS-ошибка</td>
<td>Неверный <code>origin</code> в <code>cors()</code></td>
<td>Указать адрес React-приложения</td>
</tr>
<tr>
<td>Письмо не приходит</td>
<td>SMTP не разрешён в настройках Яндекса</td>
<td>Включить SMTP в настройках почты</td>
</tr>
</tbody>
</table>
<hr />
<h2>Что добавить в продакшене</h2>
<ul>
<li><strong>Rate limiting</strong> - <code>express-rate-limit</code>, чтобы ограничить спам.</li>
<li><strong>Валидация</strong> - <code>zod</code> или <code>joi</code> для проверки данных на сервере.</li>
<li><strong>Переменные окружения на сервере</strong> - не <code>.env</code> файл, а переменные хостинга.</li>
<li><strong>HTTPS</strong> - обязательно при развёртывании.</li>
</ul>
<hr />
<p dir="auto"><strong>Порт использовать для Яндекс SMTP</strong><br />
<code>465</code> (SSL). Можно <code>587</code> с <code>secure: false</code> и STARTTLS.</p>
<p dir="auto"><strong>Как проверить, что письмо уходит, до подключения React?</strong><br />
Протестировать маршрут через Postman или <code>curl</code>:</p>
<pre><code class="language-bash">curl -X POST http://localhost:3001/api/callback \
  -H "Content-Type: application/json" \
  -d '{"name":"Тест","phone":"+79001234567"}'
</code></pre>
]]></description><link>https://forum.exlends.ru/topic/2262/forma-obratnogo-zvonka-na-react-express-yandeks-smtp-otpravka-form-obratnyh-svyazej-cherez-smtp</link><generator>RSS for Node</generator><lastBuildDate>Fri, 14 Aug 2026 16:14:28 GMT</lastBuildDate><atom:link href="https://forum.exlends.ru/topic/2262.rss" rel="self" type="application/rss+xml"/><pubDate>Wed, 10 Jun 2026 11:10:48 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Форма обратного звонка на React + Express + Яндекс SMTP &#x2F; Отправка форм обратных связей через SMTP on Wed, 10 Jun 2026 11:15:24 GMT]]></title><description><![CDATA[<h2>Как получить пароль приложения для Яндекс почты</h2>
<p dir="auto">Заходим в настройки почты</p>
<p dir="auto"><img src="/assets/uploads/files/cf/10/8b/1781089881341-0c693338-e460-4eb1-9bd7-876cf010ef69-image.webp" alt="0c693338-e460-4eb1-9bd7-876cf010ef69-image.jpeg" class=" img-fluid img-markdown" /></p>
<p dir="auto">Далее проваливаемся в почтовые программы</p>
<p dir="auto"><img src="/assets/uploads/files/05/56/54/1781089890233-99e36ddc-39c3-474d-aa52-ae8dd5b90448-image.webp" alt="99e36ddc-39c3-474d-aa52-ae8dd5b90448-image.jpeg" class=" img-fluid img-markdown" /></p>
<p dir="auto">Видим разрашения доступов</p>
<p dir="auto"><img src="/assets/uploads/files/a3/be/d4/1781089896691-f1ad9bea-dff3-4129-a698-659fa057c06f-image.webp" alt="f1ad9bea-dff3-4129-a698-659fa057c06f-image.jpeg" class=" img-fluid img-markdown" /></p>
<p dir="auto">Тыкаем на пароли приложения</p>
<p dir="auto"><img src="/assets/uploads/files/83/2c/42/1781089901577-2a2fa9f4-054a-4477-ae5f-e9c22268b998-image.webp" alt="2a2fa9f4-054a-4477-ae5f-e9c22268b998-image.jpeg" class=" img-fluid img-markdown" /></p>
<p dir="auto">Яндекс опять показывает кучу всего не нужного, тыкаем на “Безопасность”</p>
<p dir="auto"><img src="/assets/uploads/files/56/22/9d/1781089907155-617348df-dc05-4357-a5b6-3dbb9c9c0830-image.webp" alt="617348df-dc05-4357-a5b6-3dbb9c9c0830-image.jpeg" class=" img-fluid img-markdown" /></p>
<p dir="auto">И вот тут наконец-то мы видим наши долгожданные пароли приложений если прокрутить вниз</p>
<p dir="auto"><img src="/assets/uploads/files/af/eb/75/1781089914494-0d2a66b2-fcf8-4e80-ad2a-c5cd7d415b4e-image.webp" alt="0d2a66b2-fcf8-4e80-ad2a-c5cd7d415b4e-image.jpeg" class=" img-fluid img-markdown" /></p>
<p dir="auto">Далее создаем пароль приложения, кстати важно! Пароль показывается один раз, так что запишите его.</p>
<p dir="auto"><img src="/assets/uploads/files/6e/47/cd/1781089921069-61868799-8d86-4cd8-8ef5-ed64a7a6377d-image.webp" alt="61868799-8d86-4cd8-8ef5-ed64a7a6377d-image.jpeg" class=" img-fluid img-markdown" /></p>
<p dir="auto">Ну, а кому лень тыкать и понимать где находятся пароли приложения, вот ссылка - <a href="https://id.yandex.ru/security/app-passwords" target="_blank" rel="noopener noreferrer">https://id.yandex.ru/security/app-passwords</a> по ней сразу перейдете в нужный раздел, только убедитесь что вы залогинены в нужной вам почте.</p>
]]></description><link>https://forum.exlends.ru/post/3230</link><guid isPermaLink="true">https://forum.exlends.ru/post/3230</guid><dc:creator><![CDATA[kirilljsx]]></dc:creator><pubDate>Wed, 10 Jun 2026 11:15:24 GMT</pubDate></item></channel></rss>