Documentation
¶
Overview ¶
Package mailinfra — официальный Go SDK MailInfra.
Быстрый старт:
package main
import (
"context"
"fmt"
"log"
"github.com/g7AzaZLO/mailinfra-go"
)
func main() {
client, err := mailinfra.New() // ключ из MAILINFRA_API_KEY
if err != nil {
log.Fatal(err)
}
result, err := client.Emails.Send(context.Background(), mailinfra.SendParams{
From: "hello@mail.example.ru",
To: []string{"user@example.com"},
Subject: "Добро пожаловать!",
HTML: "<p>Привет!</p>",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(result.ID, result.Status)
}
Что клиент делает за вас:
- ставит Idempotency-Key на каждую отправку и переиспользует его при повторах — сбой сети не превращается в два письма;
- повторяет 429/5xx/таймауты с экспоненциальным backoff и уважает Retry-After;
- отдаёт ошибки API часовыми значениями для errors.Is (ErrRateLimit, ErrDomainNotVerified, …) и структурой *Error для errors.As;
- проверяет ограничения письма локально до сетевого вызова;
- проверяет подписи вебхуков (ConstructWebhookEvent).
Покрытие v1 — публичный API писем (/v1/emails*). Управление доменами, шаблонами, вебхуками и ключами — Management API на сессионном токене, оно в SDK пока не входит.
Ошибки ¶
Ветвиться по причине отказа надо через errors.Is, а метаданные для логов брать через errors.As:
result, err := client.Emails.Send(ctx, params)
switch {
case err == nil:
// принято
case errors.Is(err, mailinfra.ErrDomainNotVerified):
// домен отправителя ещё не подтверждён
case errors.Is(err, mailinfra.ErrRateLimit):
var apiErr *mailinfra.Error
errors.As(err, &apiErr)
// apiErr.RetryAfter — сколько просил подождать сервер
default:
return err
}
Потокобезопасность ¶
Client можно держать один на приложение и вызывать из нескольких горутин: состояние запроса в нём не хранится.
Index ¶
- Constants
- Variables
- func SignWebhookPayload(body []byte, secret string, timestamp int64) string
- func VerifyWebhookSignature(body []byte, secret, signatureHeader string, opts ...WebhookOption) bool
- type Attachment
- type BatchSendError
- type BatchSendResponseData
- type BatchSendResult
- type BounceCategory
- type BounceType
- type Client
- type DkimSigningStatus
- type EmailDetail
- type EmailEvent
- type EmailEventType
- type EmailListItem
- type EmailSendResponseData
- type EmailSource
- type EmailStatus
- type EmailsService
- func (s *EmailsService) Events(ctx context.Context, emailID string) ([]EmailEvent, error)
- func (s *EmailsService) Get(ctx context.Context, emailID string) (*EmailDetail, error)
- func (s *EmailsService) List(ctx context.Context, params ListParams) ([]EmailListItem, error)
- func (s *EmailsService) Send(ctx context.Context, params SendParams) (*SendResult, error)
- func (s *EmailsService) SendBatch(ctx context.Context, emails []SendParams) (*BatchSendResponseData, error)
- type Environment
- type Error
- type ListParams
- type Option
- func WithAPIKey(apiKey string) Option
- func WithAutoIdempotency(enabled bool) Option
- func WithBaseURL(baseURL string) Option
- func WithHTTPClient(httpClient *http.Client) Option
- func WithMaxRetries(maxRetries int) Option
- func WithRequireEnvironment(environment Environment) Option
- func WithRetryPolicy(policy RetryPolicy) Option
- func WithTimeout(timeout time.Duration) Option
- func WithUserAgentSuffix(suffix string) Option
- type RetryPolicy
- type SendParams
- type SendResult
- type SuppressionReason
- type ValidationField
- type WebhookEvent
- type WebhookOption
- type WebhookSignatureError
Examples ¶
Constants ¶
const ( // APIKeyEnvVar — переменная окружения с API-ключом проекта. APIKeyEnvVar = "MAILINFRA_API_KEY" // BaseURLEnvVar — переменная окружения с базой API. BaseURLEnvVar = "MAILINFRA_BASE_URL" )
Переменные окружения, из которых клиент берёт настройки, если их не передали явно.
const ( // MaxRecipients — потолок адресов в каждом из полей To, CC и BCC. MaxRecipients = 50 // MaxReplyTo — потолок адресов в Reply-To. MaxReplyTo = 10 // MaxTags — потолок тегов письма. MaxTags = 10 // MaxSubjectLength — потолок длины темы. MaxSubjectLength = 998 // MaxFromNameLength — потолок длины отображаемого имени отправителя. MaxFromNameLength = 200 // MaxTemplateLength — потолок длины слага шаблона. MaxTemplateLength = 100 // MaxAttachments — потолок числа вложений в письме. MaxAttachments = 10 // MaxAttachmentBase64Length — потолок длины base64-содержимого вложения. MaxAttachmentBase64Length = 14348938 // MaxFilenameLength — потолок длины имени файла вложения. MaxFilenameLength = 255 // MaxContentTypeLength — потолок длины MIME-типа вложения. MaxContentTypeLength = 255 // MaxBatchSize — потолок числа писем в одном батче. MaxBatchSize = 100 // IdempotencyKeyMaxLength — потолок длины Idempotency-Key (бэкенд // отклоняет длиннее с 422). IdempotencyKeyMaxLength = 255 )
Ограничения запроса, зафиксированные в https://docs.mailinfra.ru/openapi/mailinfra-public.json.
Проверяем их на клиенте, чтобы не тратить запрос: API вернул бы 422, но батч с вложениями уезжает целиком (до ~25 МиБ на письмо) только чтобы вернуться с ошибкой. Совпадение констант со спекой проверяет spec_parity_test.go.
const ( // WebhookSignatureHeader — заголовок с подписью доставки. WebhookSignatureHeader = "X-MailInfra-Signature" // WebhookEventIDHeader — заголовок с идентификатором события; удобен для // дедупликации доставок на вашей стороне. WebhookEventIDHeader = "X-MailInfra-Event-Id" // DefaultWebhookMaxAge — допустимый возраст подписи. // // Бэкенд отклоняет подписи старше 5 минут (SIGNATURE_MAX_AGE_SEC), держим // то же окно, чтобы поведение получателя совпадало с ожиданиями отправителя. DefaultWebhookMaxAge = 5 * time.Minute )
const ( // WebhookReasonInvalidSecret — secret пуст. WebhookReasonInvalidSecret = "invalid_secret" // WebhookReasonMalformedHeader — заголовок подписи не разобран. WebhookReasonMalformedHeader = "malformed_header" // WebhookReasonTooOld — подпись старше допустимого возраста (возможен replay). WebhookReasonTooOld = "too_old" // WebhookReasonSignatureMismatch — подпись не совпала с телом. WebhookReasonSignatureMismatch = "signature_mismatch" // WebhookReasonInvalidJSON — тело не является JSON-объектом. WebhookReasonInvalidJSON = "invalid_json" // WebhookReasonInvalidPayload — событие пришло не по контракту. WebhookReasonInvalidPayload = "invalid_payload" )
Причины отказа (поле Reason у WebhookSignatureError).
const DefaultBaseURL = "https://app.mailinfra.ru/v1"
DefaultBaseURL — продакшен-база Public API.
const DefaultTimeout = 30 * time.Second
DefaultTimeout — таймаут одного запроса, если вызывающий не задал свой *http.Client.
const Version = "0.1.0"
Version — версия SDK; уезжает в User-Agent каждого запроса.
Тег релиза в github.com/g7AzaZLO/mailinfra-go берётся отсюда: его ставит sdk/scripts/release-mirror.sh в монорепозитории MailInfra.
Variables ¶
var ( // ErrConnection — не удалось получить ответ: DNS, TCP, TLS, обрыв соединения. ErrConnection = errors.New("mailinfra: не удалось соединиться с API") // ErrTimeout — запрос не успел уложиться в таймаут. Вкладывается в // ErrConnection: ответа не было в обоих случаях. ErrTimeout = errors.New("mailinfra: истёк таймаут запроса") // ErrBadRequest — 400, запрос некорректен. ErrBadRequest = errors.New("mailinfra: некорректный запрос") // ErrAuthentication — 401, API-ключ отсутствует, невалиден или отозван. ErrAuthentication = errors.New("mailinfra: ошибка аутентификации") // ErrPermissionDenied — 403, у ключа нет прав на операцию: ключ выпущен с // правами READ_ONLY (запрещена отправка) или SENDING_ONLY (запрещено чтение). ErrPermissionDenied = errors.New("mailinfra: недостаточно прав") // ErrNotFound — 404, ресурс не найден или принадлежит другому проекту. ErrNotFound = errors.New("mailinfra: ресурс не найден") // ErrConflict — 409, конфликт состояния. ErrConflict = errors.New("mailinfra: конфликт состояния") // ErrUnprocessable — 422, не прошла валидация полей. ErrUnprocessable = errors.New("mailinfra: запрос не прошёл валидацию") // ErrRateLimit — 429, исчерпан лимит отправки: месячный объём или // дневной потолок организации. ErrRateLimit = errors.New("mailinfra: превышен лимит запросов") // ErrServer — 5xx, ошибка на стороне MailInfra. Такие запросы SDK ретраит сам. ErrServer = errors.New("mailinfra: ошибка на стороне API") // ErrAPI — ответ по статусу, для которого отдельной категории нет. ErrAPI = errors.New("mailinfra: ошибка API") )
Часовые ошибки категорий — по HTTP-статусу ответа.
var ( // ErrDomainNotVerified — домен отправителя ещё не подтверждён (DNS-записи не сошлись). ErrDomainNotVerified = errors.New("mailinfra: домен не подтверждён") // ErrDomainNotAttached — домен не привязан к проекту, которому принадлежит ключ. ErrDomainNotAttached = errors.New("mailinfra: домен не привязан к проекту") // ErrAllRecipientsSuppressed — все получатели в списке подавления, отправлять некому. ErrAllRecipientsSuppressed = errors.New("mailinfra: все получатели в списке подавления") // ErrSandboxRecipientNotAllowed — пробное письмо с sandbox-домена (LIVE-ключ) уходит только владельцу ключа. ErrSandboxRecipientNotAllowed = errors.New("mailinfra: пробное письмо с sandbox-домена — только владельцу ключа") // ErrSandboxTrialExhausted — пробная sandbox-отправка уже израсходована; нужен свой домен. ErrSandboxTrialExhausted = errors.New("mailinfra: пробная sandbox-отправка израсходована") // ErrTemplateRender — шаблон не отрендерился. ErrTemplateRender = errors.New("mailinfra: шаблон не отрендерился") // ErrTemplateVariableMissing — в variables не хватает переменной, которую требует шаблон. ErrTemplateVariableMissing = errors.New("mailinfra: не хватает переменной шаблона") // ErrReservedEmailHeader — попытка переопределить системный заголовок письма. ErrReservedEmailHeader = errors.New("mailinfra: системный заголовок письма нельзя переопределить") // ErrIdempotencyInFlight — запрос с таким Idempotency-Key ещё выполняется, повторите позже. ErrIdempotencyInFlight = errors.New("mailinfra: запрос с этим Idempotency-Key ещё выполняется") // ErrLegalDocumentsNotAccepted — не приняты обязательные юридические документы; // список ждущих согласия — в Details["pending"]. ErrLegalDocumentsNotAccepted = errors.New("mailinfra: не приняты юридические документы") )
Часовые ошибки конкретных причин — по `error.code` из ответа.
var ( // ErrInvalidRequest — параметры вызова не проходят ограничения спеки. ErrInvalidRequest = errors.New("mailinfra: некорректные параметры запроса") // ErrMalformedResponse — ответ успешен, но не по контракту. ErrMalformedResponse = errors.New("mailinfra: ответ API не по контракту") )
Часовые ошибки клиентской валидации — запрос до сети не доходит.
var ErrWebhookSignature = errors.New("mailinfra: подпись вебхука не прошла проверку")
ErrWebhookSignature — часовая ошибка для errors.Is: подпись не прошла проверку. Конкретная причина — в поле Reason у *WebhookSignatureError.
Functions ¶
func SignWebhookPayload ¶
SignWebhookPayload считает hex-подпись тела — та же функция, что на бэкенде.
Нужна в первую очередь тестам и локальной отладке обработчика: позволяет собрать корректный X-MailInfra-Signature без реального вебхука.
func VerifyWebhookSignature ¶
func VerifyWebhookSignature(body []byte, secret, signatureHeader string, opts ...WebhookOption) bool
VerifyWebhookSignature проверяет подпись, не возвращая ошибки.
Удобно, когда причина отказа неважна и нужен только ответ «пускать или нет». Если причина нужна для логов — берите ConstructWebhookEvent.
Types ¶
type Attachment ¶
type Attachment struct {
// Filename — имя файла в письме.
Filename string
// Content — сырые байты файла.
Content []byte
// ContentBase64 — готовая base64-строка (альтернатива Content).
ContentBase64 string
// ContentType — MIME-тип; пустая строка означает application/octet-stream.
ContentType string
}
Attachment — вложение письма.
Передайте Content сырыми байтами, и SDK сам закодирует их в base64; если base64 уже готов — положите его в ContentBase64. Указывать оба поля нельзя.
type BatchSendError ¶
type BatchSendError struct {
StatusCode int `json:"status_code"`
Code string `json:"code"`
Message string `json:"message"`
Details map[string]any `json:"details"`
}
BatchSendError — ошибка одного письма внутри батча.
type BatchSendResponseData ¶
type BatchSendResponseData struct {
Results []BatchSendResult `json:"results"`
AcceptedCount int `json:"accepted_count"`
FailedCount int `json:"failed_count"`
}
BatchSendResponseData — результат пакетной отправки.
type BatchSendResult ¶
type BatchSendResult struct {
// Index — позиция письма в исходном списке.
Index int `json:"index"`
// ID — идентификатор принятого письма; nil, если письмо отклонено.
ID *string `json:"id"`
// Status — статус принятого письма; nil, если письмо отклонено.
Status *EmailStatus `json:"status"`
// SkippedRecipients — отброшенные получатели с причиной в значении.
SkippedRecipients []map[string]string `json:"skipped_recipients"`
// Error — причина отказа; nil, если письмо принято.
Error *BatchSendError `json:"error"`
}
BatchSendResult — результат одного письма внутри батча.
type BounceCategory ¶
type BounceCategory string
BounceCategory — причина недоставки.
const ( BounceInvalidRecipient BounceCategory = "invalid-recipient" BounceMailboxFull BounceCategory = "mailbox-full" BounceMessageTooLarge BounceCategory = "message-too-large" BounceContentSpamBlock BounceCategory = "content-spam-block" BouncePolicyRejection BounceCategory = "policy-rejection" BounceReputationBlocklist BounceCategory = "reputation-blocklist" BounceGreylisting BounceCategory = "greylisting" BounceDNSMXFailure BounceCategory = "dns-mx-failure" BounceConnectionError BounceCategory = "connection-error" BounceRateLimited BounceCategory = "rate-limited" BounceAuthFailure BounceCategory = "auth-failure" BounceUnknown BounceCategory = "unknown" )
Значения BounceCategory.
type BounceType ¶
type BounceType string
BounceType — характер недоставки: постоянная или временная.
const ( // BounceHard — постоянный отказ: адрес в suppression после первого же случая. BounceHard BounceType = "hard" // BounceSoft — временный отказ: имеет смысл повторить позже. BounceSoft BounceType = "soft" )
Значения BounceType.
type Client ¶
type Client struct {
// Emails — письма: Send, SendBatch, Get, List, Events.
Emails *EmailsService
// contains filtered or unexported fields
}
Client — клиент Public API MailInfra.
Создаётся через New и безопасен для параллельного использования: состояние запроса в нём не хранится.
client, err := mailinfra.New() // ключ из MAILINFRA_API_KEY
if err != nil {
return err
}
result, err := client.Emails.Send(ctx, mailinfra.SendParams{
From: "hello@mail.example.ru",
To: []string{"user@example.com"},
Subject: "Привет",
HTML: "<p>Привет!</p>",
})
Example (Send) ¶
package main
import (
"context"
"fmt"
"log"
mailinfra "github.com/g7AzaZLO/mailinfra-go"
)
func main() {
client, err := mailinfra.New() // ключ из MAILINFRA_API_KEY
if err != nil {
log.Fatal(err)
}
result, err := client.Emails.Send(context.Background(), mailinfra.SendParams{
From: "hello@mail.example.ru",
To: []string{"user@example.com"},
Subject: "Добро пожаловать!",
HTML: "<p>Привет!</p>",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(result.ID, result.Status)
}
Output:
func New ¶
New создаёт клиент.
Без WithAPIKey ключ берётся из переменной окружения MAILINFRA_API_KEY. Ошибка возвращается сразу, если ключа нет или его префикс не распознан: упасть при создании клиента лучше, чем получить 401 на первой отправке.
func (*Client) AutoIdempotency ¶
AutoIdempotency сообщает, ставит ли клиент Idempotency-Key сам.
func (*Client) Environment ¶
func (c *Client) Environment() Environment
Environment возвращает окружение ключа, распознанное по его префиксу.
func (*Client) RetryPolicy ¶
func (c *Client) RetryPolicy() RetryPolicy
RetryPolicy возвращает действующую политику повторов.
type DkimSigningStatus ¶
type DkimSigningStatus string
DkimSigningStatus — что известно про DKIM-подпись письма.
const ( DkimExpected DkimSigningStatus = "expected" DkimConfirmed DkimSigningStatus = "confirmed" DkimFailed DkimSigningStatus = "failed" DkimNotSigned DkimSigningStatus = "not_signed" )
Значения DkimSigningStatus.
type EmailDetail ¶
type EmailDetail struct {
ID string `json:"id"`
ProjectID string `json:"project_id"`
FromEmail string `json:"from_email"`
FromName *string `json:"from_name"`
ToEmails []string `json:"to_emails"`
CCEmails []string `json:"cc_emails"`
BCCEmails []string `json:"bcc_emails"`
ReplyTo []string `json:"reply_to"`
Subject string `json:"subject"`
HTMLBody *string `json:"html_body"`
TextBody *string `json:"text_body"`
Headers map[string]string `json:"headers"`
Status EmailStatus `json:"status"`
Source EmailSource `json:"source"`
Tags []string `json:"tags"`
CreatedAt time.Time `json:"created_at"`
SentAt *time.Time `json:"sent_at"`
DeliveredAt *time.Time `json:"delivered_at"`
FailedAt *time.Time `json:"failed_at"`
MessageID *string `json:"message_id"`
BounceType *BounceType `json:"bounce_type"`
BounceCategory *BounceCategory `json:"bounce_category"`
SuppressionReason *SuppressionReason `json:"suppression_reason"`
FinalSMTPCode *int `json:"final_smtp_code"`
FinalSMTPEnhanced *string `json:"final_smtp_enhanced"`
FinalSMTPText *string `json:"final_smtp_text"`
SPFDomain *string `json:"spf_domain"`
SPFAligned *bool `json:"spf_aligned"`
SPFAuthorizedIP *bool `json:"spf_authorized_ip"`
DkimD *string `json:"dkim_d"`
DkimSelector *string `json:"dkim_selector"`
DkimAligned *bool `json:"dkim_aligned"`
DkimSigningStatus *DkimSigningStatus `json:"dkim_signing_status"`
DmarcAligned *bool `json:"dmarc_aligned"`
}
EmailDetail — письмо с телом, заголовками и диагностикой доставки.
type EmailEvent ¶
type EmailEvent struct {
ID string `json:"id"`
Type EmailEventType `json:"type"`
Data map[string]any `json:"data"`
Timestamp time.Time `json:"timestamp"`
IPAddress *string `json:"ip_address"`
UserAgent *string `json:"user_agent"`
}
EmailEvent — событие жизненного цикла письма.
type EmailEventType ¶
type EmailEventType string
EmailEventType — тип события жизненного цикла письма.
const ( EmailEventQueued EmailEventType = "queued" EmailEventSending EmailEventType = "sending" EmailEventSent EmailEventType = "sent" EmailEventDelivered EmailEventType = "delivered" EmailEventSMTPConnected EmailEventType = "smtp_connected" EmailEventOpened EmailEventType = "opened" EmailEventClicked EmailEventType = "clicked" EmailEventBounced EmailEventType = "bounced" EmailEventComplained EmailEventType = "complained" EmailEventFailed EmailEventType = "failed" EmailEventSimulated EmailEventType = "simulated" // EmailEventDeferred — временный отказ сервера получателя, MTA повторит попытку. EmailEventDeferred EmailEventType = "deferred" )
Значения EmailEventType.
type EmailListItem ¶
type EmailListItem struct {
ID string `json:"id"`
ProjectID string `json:"project_id"`
FromEmail string `json:"from_email"`
ToEmails []string `json:"to_emails"`
Subject string `json:"subject"`
Status EmailStatus `json:"status"`
Source EmailSource `json:"source"`
Tags []string `json:"tags"`
CreatedAt time.Time `json:"created_at"`
SentAt *time.Time `json:"sent_at"`
DeliveredAt *time.Time `json:"delivered_at"`
MessageID *string `json:"message_id"`
BounceType *BounceType `json:"bounce_type"`
BounceCategory *BounceCategory `json:"bounce_category"`
SuppressionReason *SuppressionReason `json:"suppression_reason"`
FinalSMTPCode *int `json:"final_smtp_code"`
FinalSMTPEnhanced *string `json:"final_smtp_enhanced"`
FinalSMTPText *string `json:"final_smtp_text"`
SPFDomain *string `json:"spf_domain"`
SPFAligned *bool `json:"spf_aligned"`
SPFAuthorizedIP *bool `json:"spf_authorized_ip"`
DkimD *string `json:"dkim_d"`
DkimSelector *string `json:"dkim_selector"`
DkimAligned *bool `json:"dkim_aligned"`
DkimSigningStatus *DkimSigningStatus `json:"dkim_signing_status"`
DmarcAligned *bool `json:"dmarc_aligned"`
}
EmailListItem — письмо в списке.
type EmailSendResponseData ¶
type EmailSendResponseData struct {
// ID — идентификатор письма в MailInfra.
ID string `json:"id"`
// Status — статус сразу после приёма: QUEUED либо SIMULATED для sandbox-ключа.
Status EmailStatus `json:"status"`
// SkippedRecipients — получатели, которых API отбросил (например, адрес в
// списке подавления), с причиной в значении.
SkippedRecipients []map[string]string `json:"skipped_recipients"`
}
EmailSendResponseData — данные успешной отправки.
type EmailSource ¶
type EmailSource string
EmailSource — транспорт, через который письмо попало в отправку.
const ( EmailSourceAPI EmailSource = "API" EmailSourceSMTP EmailSource = "SMTP" EmailSourceCampaign EmailSource = "CAMPAIGN" )
Значения EmailSource.
type EmailStatus ¶
type EmailStatus string
EmailStatus — статус письма.
const ( // EmailStatusQueued — письмо принято и ждёт отправки. EmailStatusQueued EmailStatus = "QUEUED" // EmailStatusSending — письмо передаётся MTA. EmailStatusSending EmailStatus = "SENDING" // EmailStatusSent — письмо принято сервером получателя. EmailStatusSent EmailStatus = "SENT" // EmailStatusDelivered — доставка подтверждена. EmailStatusDelivered EmailStatus = "DELIVERED" // EmailStatusBounced — письмо отбито (см. BounceType и BounceCategory). EmailStatusBounced EmailStatus = "BOUNCED" // EmailStatusComplained — получатель пожаловался на спам. EmailStatusComplained EmailStatus = "COMPLAINED" // EmailStatusFailed — отправка не удалась. EmailStatusFailed EmailStatus = "FAILED" // EmailStatusSimulated — sandbox-отправка по ключу mi_test_…: письмо // сохранено, но не доставлено и не расходует месячный объём. EmailStatusSimulated EmailStatus = "SIMULATED" )
Значения EmailStatus.
type EmailsService ¶
type EmailsService struct {
// contains filtered or unexported fields
}
EmailsService — письма проекта. Доступен как client.Emails.
func (*EmailsService) Events ¶
func (s *EmailsService) Events(ctx context.Context, emailID string) ([]EmailEvent, error)
Events возвращает события письма в хронологическом порядке.
func (*EmailsService) Get ¶
func (s *EmailsService) Get(ctx context.Context, emailID string) (*EmailDetail, error)
Get возвращает письмо с телом, заголовками и диагностикой доставки.
func (*EmailsService) List ¶
func (s *EmailsService) List(ctx context.Context, params ListParams) ([]EmailListItem, error)
List возвращает письма проекта по фильтрам.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
mailinfra "github.com/g7AzaZLO/mailinfra-go"
)
func main() {
client, err := mailinfra.New()
if err != nil {
log.Fatal(err)
}
// From и To — интервал по времени создания письма, а не адреса.
emails, err := client.Emails.List(context.Background(), mailinfra.ListParams{
Status: mailinfra.EmailStatusBounced,
Tag: "order",
From: time.Now().Add(-24 * time.Hour),
Limit: 50,
})
if err != nil {
log.Fatal(err)
}
fmt.Println("отбито писем за сутки:", len(emails))
}
Output:
func (*EmailsService) Send ¶
func (s *EmailsService) Send(ctx context.Context, params SendParams) (*SendResult, error)
Send отправляет письмо.
Возвращает идентификатор, статус, отброшенных получателей, флаг идемпотентного повтора и использованный ключ идемпотентности.
Example (IdempotencyKey) ¶
Ключ идемпотентности, привязанный к бизнес-событию, делает повторный вызов безопасным: второй раз письмо не уйдёт.
package main
import (
"context"
"fmt"
"log"
mailinfra "github.com/g7AzaZLO/mailinfra-go"
)
func main() {
client, err := mailinfra.New()
if err != nil {
log.Fatal(err)
}
result, err := client.Emails.Send(context.Background(), mailinfra.SendParams{
From: "hello@mail.example.ru",
To: []string{"user@example.com"},
Subject: "Заказ A-1024 оплачен",
HTML: "<p>Спасибо!</p>",
IdempotencyKey: "order-A-1024-paid",
})
if err != nil {
log.Fatal(err)
}
if result.Replayed {
fmt.Println("письмо уже было отправлено раньше, дубля нет")
}
}
Output:
func (*EmailsService) SendBatch ¶
func (s *EmailsService) SendBatch(ctx context.Context, emails []SendParams) (*BatchSendResponseData, error)
SendBatch отправляет до 100 писем одним запросом.
Батч возвращает 202, даже если часть писем не принята: проверяйте Error у каждого элемента Results, а не только отсутствие ошибки вызова. Idempotency-Key этот эндпоинт не поддерживает, поэтому SDK не повторяет его после 5xx, таймаута и обрыва соединения — повтор мог бы отправить часть писем дважды. Повторяется только 429.
Example ¶
Батч возвращает 202, даже если часть писем не принята: проверять надо каждый элемент, а не только отсутствие ошибки вызова.
package main
import (
"context"
"fmt"
"log"
mailinfra "github.com/g7AzaZLO/mailinfra-go"
)
func main() {
client, err := mailinfra.New()
if err != nil {
log.Fatal(err)
}
batch, err := client.Emails.SendBatch(context.Background(), []mailinfra.SendParams{
{From: "hello@mail.example.ru", To: []string{"a@example.com"}, Subject: "Раз", Text: "1"},
{From: "hello@mail.example.ru", To: []string{"b@example.com"}, Subject: "Два", Text: "2"},
})
if err != nil {
log.Fatal(err)
}
for _, item := range batch.Results {
if item.Error != nil {
fmt.Printf("письмо #%d отклонено: %s\n", item.Index, item.Error.Code)
}
}
}
Output:
type Environment ¶
type Environment string
Environment — окружение API-ключа.
const ( // EnvironmentLive — рабочий ключ mi_live_…: письма уходят получателям. EnvironmentLive Environment = "live" // EnvironmentTest — sandbox-ключ mi_test_…: письмо сохраняется со статусом // SIMULATED, не доставляется и не расходует месячный объём. EnvironmentTest Environment = "test" )
Значения Environment.
type Error ¶
type Error struct {
// Message — человеко-читаемое сообщение от API (на русском).
Message string
// StatusCode — HTTP-статус ответа; 0 для сетевых ошибок и клиентской валидации.
StatusCode int
// Code — машинный код ошибки (error.code).
Code string
// Details — безопасные структурированные детали (error.details).
Details map[string]any
// RequestID — идентификатор запроса для поддержки (он же заголовок X-Request-ID).
RequestID string
// RetryAfter — сколько просил подождать сервер; значимо только вместе с
// HasRetryAfter.
RetryAfter time.Duration
// HasRetryAfter — был ли в ответе разобранный заголовок Retry-After.
HasRetryAfter bool
// contains filtered or unexported fields
}
Error — ошибка вызова MailInfra: и ответ API, и транспортный сбой, и отказ клиентской валидации.
Ловить её целиком удобно через errors.As, а ветвиться — через errors.Is по часовым ошибкам (см. описание пакета).
Example ¶
Ветвиться по причине отказа надо через errors.Is, а метаданные для логов брать через errors.As.
package main
import (
"context"
"errors"
"fmt"
"log"
mailinfra "github.com/g7AzaZLO/mailinfra-go"
)
func main() {
client, err := mailinfra.New()
if err != nil {
log.Fatal(err)
}
_, err = client.Emails.Send(context.Background(), mailinfra.SendParams{
From: "hello@mail.example.ru",
To: []string{"user@example.com"},
Subject: "Привет",
Text: "Привет!",
})
switch {
case err == nil:
fmt.Println("принято")
case errors.Is(err, mailinfra.ErrDomainNotVerified):
fmt.Println("домен отправителя ещё не подтверждён")
case errors.Is(err, mailinfra.ErrRateLimit):
var apiErr *mailinfra.Error
errors.As(err, &apiErr)
fmt.Println("лимит, повторить через", apiErr.RetryAfter)
case errors.Is(err, mailinfra.ErrUnprocessable):
var apiErr *mailinfra.Error
errors.As(err, &apiErr)
for _, field := range apiErr.Fields() {
fmt.Printf("%s: %s\n", field.Field, field.Message)
}
default:
log.Fatal(err)
}
}
Output:
func (*Error) Error ¶
Error возвращает текст ошибки с машинным кодом, статусом и request_id — всем, что нужно для обращения в поддержку.
func (*Error) Fields ¶
func (e *Error) Fields() []ValidationField
Fields возвращает пер-полевые причины отказа валидации (422 с кодом request_validation_failed). Пустой срез, если детализации нет.
type ListParams ¶
type ListParams struct {
// Status — фильтр по статусу.
Status EmailStatus
// FromEmail — фильтр по отправителю.
FromEmail string
// ToEmail — фильтр по получателю.
ToEmail string
// Tag — фильтр по тегу.
Tag string
// From — начало интервала по времени создания.
From time.Time
// To — конец интервала по времени создания.
To time.Time
// Limit — размер выборки, 1..100 (по умолчанию 25 на стороне API).
Limit int
}
ListParams — фильтры выборки писем. Нулевое значение поля означает «без фильтра».
type Option ¶
type Option func(*options)
Option — настройка клиента, передаётся в New.
func WithAPIKey ¶
WithAPIKey задаёт API-ключ проекта. По умолчанию ключ берётся из MAILINFRA_API_KEY.
func WithAutoIdempotency ¶
WithAutoIdempotency включает или выключает автоматический Idempotency-Key на отправке письма. По умолчанию включён.
Выключать стоит только если ключ вы генерируете сами: без него бэкенду нечем распознать повтор, и SDK перестаёт ретраить отправку после 5xx и таймаутов.
func WithBaseURL ¶
WithBaseURL задаёт базу API. По умолчанию — MAILINFRA_BASE_URL либо DefaultBaseURL.
func WithHTTPClient ¶
WithHTTPClient подставляет свой *http.Client — прокси, пул соединений, mTLS.
Таймаут такого клиента SDK не перетирает: без явного WithTimeout собственный дедлайн не ставится вовсе, и запрос ограничивают только ваши настройки.
func WithMaxRetries ¶
WithMaxRetries задаёт число повторов сверх первой попытки. 0 — без повторов.
func WithRequireEnvironment ¶
func WithRequireEnvironment(environment Environment) Option
WithRequireEnvironment требует ключ нужного окружения: New вернёт ошибку, если префикс ключа не совпал.
Страхует от самой дорогой ошибки конфигурации — тестовый ключ в проде не отправит ни одного письма, а рабочий в тестах отправит настоящие.
func WithRetryPolicy ¶
func WithRetryPolicy(policy RetryPolicy) Option
WithRetryPolicy задаёт политику повторов. Незаполненные поля заменяются значениями по умолчанию — и MaxRetries тоже: нулевой MaxRetries в политике значит «не задан» (по умолчанию 2 повтора), а не «без повторов». Отключить повторы — WithMaxRetries(0) после WithRetryPolicy или отрицательный MaxRetries в самой политике.
func WithTimeout ¶
WithTimeout задаёт таймаут одной попытки. 0 — не ставить собственный дедлайн (полезно, если временем управляет ваш http.Client или контекст вызова).
По умолчанию — DefaultTimeout для своего клиента и 0 для переданного через WithHTTPClient.
func WithUserAgentSuffix ¶
WithUserAgentSuffix добавляет к User-Agent имя вашей интеграции.
type RetryPolicy ¶
type RetryPolicy struct {
// MaxRetries — сколько раз повторить сверх первой попытки. Как и у
// остальных полей, 0 в WithRetryPolicy — «не задан» (по умолчанию 2);
// отрицательное значение — повторов нет. Проще отключить повторы через
// WithMaxRetries(0).
MaxRetries int
// InitialBackoff — базовая задержка первой паузы.
InitialBackoff time.Duration
// BackoffFactor — множитель роста задержки между попытками.
BackoffFactor float64
// MaxBackoff — потолок собственной задержки.
MaxBackoff time.Duration
// MaxRetryAfter — порог доверия к `Retry-After`. Если сервер просит
// подождать дольше — SDK не спит, а отдаёт ошибку с ErrRateLimit, чтобы
// вызывающий сам решил, ждать или деградировать.
MaxRetryAfter time.Duration
// RetryableStatuses — статусы, повторяемые для идемпотентных запросов.
// nil — набор по умолчанию.
RetryableStatuses []int
// RetryableStatusesNonIdempotent — то же для неидемпотентных. nil —
// набор по умолчанию.
RetryableStatusesNonIdempotent []int
}
RetryPolicy — настройки повторов.
Нулевое значение полей означает «взять дефолт», поэтому частичная настройка через WithRetryPolicy не обнуляет остальное.
func DefaultRetryPolicy ¶
func DefaultRetryPolicy() RetryPolicy
DefaultRetryPolicy возвращает политику повторов по умолчанию: две попытки сверх первой, backoff 0.5s → 1s → … с потолком 8s, доверие к `Retry-After` до минуты.
type SendParams ¶
type SendParams struct {
// From — адрес отправителя на подтверждённом домене.
From string
// FromName — отображаемое имя отправителя.
FromName string
// To — получатели, минимум один.
To []string
// CC — копия.
CC []string
// BCC — скрытая копия.
BCC []string
// ReplyTo — адреса для ответа.
ReplyTo []string
// Subject — тема; обязательна, если письмо шлётся без шаблона.
Subject string
// HTML — HTML-часть письма.
HTML string
// Text — текстовая часть письма.
Text string
// Headers — дополнительные заголовки письма (системные запрещены).
Headers map[string]string
// Tags — теги для фильтрации в дашборде и вебхуках.
Tags []string
// Template — слаг шаблона вместо HTML/Text.
Template string
// Variables — переменные шаблона.
Variables map[string]any
// Attachments — вложения.
Attachments []Attachment
// IdempotencyKey — свой ключ идемпотентности. Пустая строка означает
// «сгенерировать», если у клиента включён автоматический ключ.
IdempotencyKey string
// WithoutIdempotencyKey отправляет письмо вообще без Idempotency-Key.
//
// Осознанный отказ от защиты: бэкенду будет нечем распознать повтор, и SDK
// перестанет ретраить этот запрос после 5xx и таймаутов.
WithoutIdempotencyKey bool
}
SendParams — параметры отправки письма.
Обязательны только From и To; остальное зависит от того, шлёте вы готовое тело (Subject + HTML/Text) или шаблон (Template + Variables).
type SendResult ¶
type SendResult struct {
EmailSendResponseData
// Replayed — true, если это идемпотентный повтор: письмо было принято
// предыдущим запросом с тем же Idempotency-Key, повторной отправки не
// произошло (ответ 200 + заголовок Idempotent-Replayed).
Replayed bool
// IdempotencyKey — ключ, с которым ушёл запрос: сгенерированный SDK или
// переданный вызывающим. Стоит залогировать: с ним повтор гарантированно
// не создаст второе письмо.
IdempotencyKey string
// RequestID — идентификатор запроса для обращений в поддержку.
RequestID string
}
SendResult — результат отправки одного письма.
type SuppressionReason ¶
type SuppressionReason string
SuppressionReason — причина попадания адреса в список подавления.
const ( SuppressionHardBounce SuppressionReason = "HARD_BOUNCE" SuppressionComplaint SuppressionReason = "COMPLAINT" SuppressionUnsubscribe SuppressionReason = "UNSUBSCRIBE" SuppressionManual SuppressionReason = "MANUAL" )
Значения SuppressionReason.
type ValidationField ¶
ValidationField — пер-полевая причина отказа валидации из Details["fields"].
type WebhookEvent ¶
type WebhookEvent struct {
// ID — идентификатор события: evt_<uuid> у событий письма,
// evt_deliverability_<hex> у deliverability.*, evt_test_ping у теста.
ID string `json:"id"`
// Type — тип: email.<событие> у событий письма (например email.bounced),
// deliverability.<инцидент> как есть (например deliverability.dns_degraded),
// test.ping у тестовой отправки из дашборда.
Type string `json:"type"`
// CreatedAt — момент события на стороне MailInfra.
CreatedAt time.Time `json:"created_at"`
// Data — полезная нагрузка. У событий письма Data["email"] — письмо,
// Data["event"] — специфика события (MX, IP, bounce-детали, URL клика).
// У deliverability.* письма нет: domain_id, domain, severity, title,
// details инцидента.
Data map[string]any `json:"data"`
}
WebhookEvent — разобранное событие вебхука.
func ConstructWebhookEvent ¶
func ConstructWebhookEvent(body []byte, secret, signatureHeader string, opts ...WebhookOption) (*WebhookEvent, error)
ConstructWebhookEvent проверяет подпись и возвращает типизированное событие.
Это рекомендуемый способ: тело нельзя разобрать раньше, чем проверена подпись, иначе обработчик доверяет неаутентифицированным данным.
Ошибка всегда *WebhookSignatureError; причина — в поле Reason.
Example ¶
Тело обязательно брать до разбора JSON: пересериализованный JSON даст другие байты и подпись не сойдётся.
package main
import (
"errors"
"io"
"log"
"net/http"
"os"
mailinfra "github.com/g7AzaZLO/mailinfra-go"
)
func main() {
http.HandleFunc("/mailinfra/events", func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "", http.StatusBadRequest)
return
}
event, err := mailinfra.ConstructWebhookEvent(
body,
os.Getenv("MAILINFRA_WEBHOOK_SECRET"),
r.Header.Get(mailinfra.WebhookSignatureHeader),
)
if err != nil {
var signatureErr *mailinfra.WebhookSignatureError
if errors.As(err, &signatureErr) {
log.Printf("подпись отклонена: %s", signatureErr.Reason)
}
http.Error(w, "", http.StatusBadRequest)
return
}
switch event.Type {
case "email.bounced":
log.Println("отбито:", event.EmailID())
case "email.delivered":
log.Println("доставлено:", event.EmailID())
}
w.WriteHeader(http.StatusOK)
})
}
Output:
func (*WebhookEvent) Email ¶
func (e *WebhookEvent) Email() map[string]any
Email возвращает письмо, к которому относится событие, или nil для событий без письма (deliverability.*, test.ping).
func (*WebhookEvent) EmailID ¶
func (e *WebhookEvent) EmailID() string
EmailID возвращает идентификатор письма или пустую строку, если события письма нет.
func (*WebhookEvent) IsTest ¶
func (e *WebhookEvent) IsTest() bool
IsTest сообщает, тестовое ли это событие («Отправить тест» в дашборде).
type WebhookOption ¶
type WebhookOption func(*webhookOptions)
WebhookOption — настройка проверки подписи.
func WithWebhookMaxAge ¶
func WithWebhookMaxAge(maxAge time.Duration) WebhookOption
WithWebhookMaxAge задаёт допустимый возраст подписи; защита от replay. Нулевое значение отключает проверку времени.
type WebhookSignatureError ¶
type WebhookSignatureError struct {
// Message — человеко-читаемое описание.
Message string
// Reason — машинная причина: одна из констант WebhookReason*.
Reason string
}
WebhookSignatureError — подпись не прошла проверку.
func (*WebhookSignatureError) Error ¶
func (e *WebhookSignatureError) Error() string
Error возвращает описание вместе с машинной причиной.
func (*WebhookSignatureError) Unwrap ¶
func (e *WebhookSignatureError) Unwrap() error
Unwrap отдаёт часовую ошибку, по которой работает errors.Is.