Skip to content

Commit ea8bdf4

Browse files
committed
ipv4 prioritization added
1 parent 3768a27 commit ea8bdf4

File tree

3 files changed

+65
-7
lines changed

3 files changed

+65
-7
lines changed

src/Communicator/Communicator.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<PackageReadmeFile>Readme.md</PackageReadmeFile>
99
<Authors>Pandatech</Authors>
1010
<Copyright>MIT</Copyright>
11-
<Version>2.2.0</Version>
11+
<Version>2.2.1</Version>
1212
<PackageId>Pandatech.Communicator</PackageId>
1313
<Title>SMS and Email Communication helper</Title>
1414
<PackageTags>pandatech;communicator;messaging;email;smtp;mailkit;mimekit;sms;twilio;dexatel;notifications;transactional;dotnet;net9;minimal-api;dependency-injection</PackageTags>

src/Communicator/Services/Implementations/EmailService.cs

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using Communicator.Helpers;
1+
using System.Net;
2+
using System.Net.Sockets;
3+
using Communicator.Helpers;
24
using Communicator.Models;
35
using Communicator.Options;
46
using Communicator.Services.Interfaces;
@@ -77,13 +79,69 @@ private static SmtpClient CreateClient(EmailConfiguration config)
7779

7880
private static async Task ConnectAndAuthAsync(SmtpClient client, EmailConfiguration config, CancellationToken ct)
7981
{
80-
var socketOptions = ResolveSocketOptions(config.SmtpPort);
81-
await client.ConnectAsync(config.SmtpServer, config.SmtpPort, socketOptions, ct);
82+
var options = ResolveSocketOptions(config.SmtpPort);
83+
client.Timeout = config.TimeoutMs;
8284

83-
if (!string.IsNullOrWhiteSpace(config.SmtpUsername))
85+
var addresses = await Dns.GetHostAddressesAsync(config.SmtpServer, ct);
86+
87+
var ordered = addresses
88+
.OrderByDescending(a => a.AddressFamily == AddressFamily.InterNetwork) // IPv4 first
89+
.ToArray();
90+
91+
Exception? last = null;
92+
93+
foreach (var ip in ordered)
8494
{
85-
await client.AuthenticateAsync(config.SmtpUsername, config.SmtpPassword, ct);
95+
NetworkStream? stream = null;
96+
97+
try
98+
{
99+
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
100+
cts.CancelAfter(config.TimeoutMs);
101+
102+
var socket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
103+
await socket.ConnectAsync(new IPEndPoint(ip, config.SmtpPort), cts.Token);
104+
105+
stream = new NetworkStream(socket, ownsSocket: true);
106+
107+
await client.ConnectAsync(stream, config.SmtpServer, config.SmtpPort, options, cts.Token);
108+
109+
stream = null; // MailKit now owns the connection
110+
111+
await client.AuthenticateAsync(config.SmtpUsername, config.SmtpPassword, cts.Token);
112+
return;
113+
}
114+
catch (Exception ex)
115+
{
116+
last = ex;
117+
118+
try
119+
{
120+
if (stream is not null)
121+
{
122+
await stream.DisposeAsync();
123+
}
124+
}
125+
catch
126+
{
127+
// ignored
128+
}
129+
130+
try
131+
{
132+
if (client.IsConnected)
133+
{
134+
await client.DisconnectAsync(true, CancellationToken.None);
135+
}
136+
}
137+
catch
138+
{
139+
// ignored
140+
}
141+
}
86142
}
143+
144+
throw last ?? new TimeoutException("SMTP connect failed.");
87145
}
88146

89147
private static SecureSocketOptions ResolveSocketOptions(int port)

test/Communicator.Demo/appsettings.Development.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"EmailConfigurations": {
1212
"GeneralSender": {
1313
"SmtpServer": "smtp.gmail.com",
14-
"SmtpPort": "465",
14+
"SmtpPort": "587",
1515
"SmtpUsername": "[email protected]",
1616
"SmtpPassword": "xemuemnwpcgxcfrh",
1717
"SenderEmail": "[email protected]",

0 commit comments

Comments
 (0)