feat: integrate Resend for OTP email delivery and update configuration
This commit is contained in:
+44
-89
@@ -2,13 +2,7 @@
|
||||
|
||||
#include <drogon/drogon.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <json/json.h>
|
||||
|
||||
namespace visione::email
|
||||
{
|
||||
@@ -19,101 +13,62 @@ std::string configString(const std::string &key, const std::string &fallback)
|
||||
const auto &mail = drogon::app().getCustomConfig()["mail"];
|
||||
return mail.get(key, fallback).asString();
|
||||
}
|
||||
|
||||
int configInt(const std::string &key, int fallback)
|
||||
{
|
||||
const auto &mail = drogon::app().getCustomConfig()["mail"];
|
||||
return mail.get(key, fallback).asInt();
|
||||
}
|
||||
|
||||
bool readResponse(int socket)
|
||||
{
|
||||
std::array<char, 512> buffer{};
|
||||
const auto count = recv(socket, buffer.data(), buffer.size() - 1, 0);
|
||||
return count > 0 && buffer[0] >= '2' && buffer[0] <= '3';
|
||||
}
|
||||
|
||||
bool sendLine(int socket, const std::string &line)
|
||||
{
|
||||
const auto payload = line + "\r\n";
|
||||
return send(socket, payload.data(), payload.size(), 0) ==
|
||||
static_cast<ssize_t>(payload.size());
|
||||
}
|
||||
|
||||
bool sendCommand(int socket, const std::string &line)
|
||||
{
|
||||
return sendLine(socket, line) && readResponse(socket);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string EmailService::deliveryName() const
|
||||
{
|
||||
return configString("provider", "resend");
|
||||
}
|
||||
|
||||
bool EmailService::sendOtp(const std::string &to, const std::string &code) const
|
||||
{
|
||||
const auto host = configString("smtp_host", "127.0.0.1");
|
||||
const auto port = std::to_string(configInt("smtp_port", 1025));
|
||||
const auto from = configString("from", "[email protected]");
|
||||
const auto apiBaseUrl = configString("api_base_url", "https://api.resend.com");
|
||||
const auto apiKey = configString("api_key", "");
|
||||
const auto from = configString("from", "");
|
||||
|
||||
addrinfo hints{};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
addrinfo *addresses = nullptr;
|
||||
if (getaddrinfo(host.c_str(), port.c_str(), &hints, &addresses) != 0)
|
||||
if (apiKey.empty() || from.empty())
|
||||
{
|
||||
LOG_ERROR << "Email delivery configuration missing: api_key or from";
|
||||
return false;
|
||||
}
|
||||
|
||||
int smtp = -1;
|
||||
for (auto *address = addresses; address != nullptr; address = address->ai_next)
|
||||
{
|
||||
smtp = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
|
||||
if (smtp < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (connect(smtp, address->ai_addr, address->ai_addrlen) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
close(smtp);
|
||||
smtp = -1;
|
||||
}
|
||||
freeaddrinfo(addresses);
|
||||
auto client = drogon::HttpClient::newHttpClient(apiBaseUrl);
|
||||
|
||||
if (smtp < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Json::Value payload;
|
||||
payload["from"] = "Visione <" + from + ">";
|
||||
payload["subject"] = "Codice OTP Visione";
|
||||
payload["text"] = "Il tuo codice OTP per Visione e': " + code +
|
||||
"\nScade tra 10 minuti.\n";
|
||||
payload["to"] = Json::arrayValue;
|
||||
payload["to"].append(to);
|
||||
|
||||
const auto closeSocket = [&smtp]() {
|
||||
if (smtp >= 0)
|
||||
{
|
||||
close(smtp);
|
||||
smtp = -1;
|
||||
}
|
||||
};
|
||||
auto request = drogon::HttpRequest::newHttpJsonRequest(payload);
|
||||
request->setMethod(drogon::Post);
|
||||
request->setPath("/emails");
|
||||
request->addHeader("Authorization", "Bearer " + apiKey);
|
||||
request->addHeader("User-Agent", "visione-backend/1.0");
|
||||
|
||||
if (!readResponse(smtp) || !sendCommand(smtp, "HELO visione.local") ||
|
||||
!sendCommand(smtp, "MAIL FROM:<" + from + ">") ||
|
||||
!sendCommand(smtp, "RCPT TO:<" + to + ">") ||
|
||||
!sendCommand(smtp, "DATA"))
|
||||
{
|
||||
closeSocket();
|
||||
return false;
|
||||
}
|
||||
client->sendRequest(
|
||||
request,
|
||||
[recipient = to](drogon::ReqResult result, const drogon::HttpResponsePtr &response) {
|
||||
if (result == drogon::ReqResult::Ok &&
|
||||
response != nullptr &&
|
||||
response->statusCode() == drogon::k200OK)
|
||||
{
|
||||
LOG_INFO << "OTP email queued via Resend for " << recipient;
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string body =
|
||||
"From: Visione <" + from + ">\r\n"
|
||||
"To: " + to + "\r\n"
|
||||
"Subject: Codice OTP Visione\r\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\r\n"
|
||||
"\r\n"
|
||||
"Il tuo codice OTP per Visione e': " + code + "\r\n"
|
||||
"Scade tra 10 minuti.\r\n"
|
||||
".";
|
||||
const auto body = response != nullptr ? response->getBody() : std::string{};
|
||||
LOG_ERROR << "OTP email delivery failed via Resend for "
|
||||
<< recipient
|
||||
<< ", result=" << static_cast<int>(result)
|
||||
<< ", status="
|
||||
<< (response != nullptr ? std::to_string(response->statusCode())
|
||||
: "none")
|
||||
<< ", body=" << body;
|
||||
});
|
||||
|
||||
const auto ok = sendCommand(smtp, body) && sendCommand(smtp, "QUIT");
|
||||
closeSocket();
|
||||
return ok;
|
||||
return true;
|
||||
}
|
||||
} // namespace visione::email
|
||||
|
||||
|
||||
+1
-1
@@ -8,6 +8,6 @@ class EmailService
|
||||
{
|
||||
public:
|
||||
bool sendOtp(const std::string &to, const std::string &code) const;
|
||||
std::string deliveryName() const;
|
||||
};
|
||||
} // namespace visione::email
|
||||
|
||||
|
||||
Reference in New Issue
Block a user