Every Acumatica instance sends email — invoices, dunning letters, order confirmations, approval requests. And in almost every implementation I've inherited, that email is going out through somebody's Office 365 mailbox with no bounce handling, no delivery visibility, and a sender reputation that lives or dies with one shared mailbox. Moving Acumatica's outbound mail to SendGrid fixes most of that, and there are two very different ways to do it: the ten-minute SMTP relay, and the deeper API integration. I've done both; here's when each one earns its keep.
Level 1: SendGrid as an SMTP relay
Acumatica's System Email Accounts screen (SM204002) speaks plain SMTP, and SendGrid exposes an SMTP endpoint. That means the simplest integration is configuration, not code:
- Host:
smtp.sendgrid.net, port 587 with STARTTLS - Username: literally the string
apikey(this trips everyone up — it is not your account name) - Password: a SendGrid API key with the Mail Send scope only
Before anything will deliver reliably you need domain authentication on the SendGrid side: the CNAME records for SPF/DKIM, and these days a DMARC record too — Gmail and Yahoo have both tightened bulk-sender requirements, and ERP-generated invoice mail absolutely counts as bulk once you're sending hundreds a day. Do the DNS work first; an unauthenticated domain sending dunning letters is a fast track to the spam folder.
With the relay in place, every existing Acumatica notification — mailing settings on Customers, email templates, business event email subscribers — flows through SendGrid untouched. You get delivery stats, suppression management, and IP reputation without changing a single screen.
Test and production instances sharing one key means a runaway test job can burn your production sender reputation, and you can't revoke one without the other. Keys are free; make two.
Level 2: the v3 Mail Send API for transactional templates
The SMTP relay keeps Acumatica's own HTML templates, which are honestly fine for internal mail and mediocre for customer-facing mail. When a client wants properly designed, versioned, marketing-approved templates, I move the customer-facing sends to SendGrid dynamic templates and call the v3 API from a business event webhook (via a small relay function) or directly from a graph extension.
The API call itself is small — the design decision is that Acumatica sends data, not HTML:
public static class SendGridClient
{
public static async Task SendInvoiceEmail(ARInvoice inv, Customer cust, string pdfBase64)
{
var payload = new {
from = new { email = "billing@client.co.ke", name = "Client Ltd Billing" },
personalizations = new[] { new {
to = new[] { new { email = cust.DefContactEmail } },
dynamic_template_data = new {
invoiceNbr = inv.RefNbr,
dueDate = inv.DueDate?.ToString("dd MMM yyyy"),
total = inv.CuryOrigDocAmt,
currency = inv.CuryID
}
}},
template_id = "d-8e2f60c1c9a54b0f9d2ab5b1c7b1e9aa",
attachments = new[] { new {
content = pdfBase64, type = "application/pdf",
filename = inv.RefNbr + ".pdf", disposition = "attachment"
}},
custom_args = new { acuRefNbr = inv.RefNbr, acuDocType = inv.DocType }
};
var req = new HttpRequestMessage(HttpMethod.Post,
"https://api.sendgrid.com/v3/mail/send");
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
req.Content = new StringContent(JsonSerializer.Serialize(payload),
Encoding.UTF8, "application/json");
var resp = await Http.SendAsync(req);
if ((int)resp.StatusCode != 202)
throw new PXException("SendGrid rejected the send: " + resp.StatusCode);
}
}
Note custom_args: whatever you put there comes back on every webhook event for that message. That's the hook that makes the next section work.
Closing the loop: the Event Webhook
The part that actually changes behaviour in the business is not sending — it's knowing what happened after. SendGrid's Event Webhook POSTs you an array of events (delivered, open, bounce, dropped, spamreport) as they happen. I point it at a small endpoint that does two things:
- On
bounceordropped: call the Acumatica contract-based REST API and write the failure onto the customer — either an activity on the record or a custom "email status" field on the contact. Credit control stops chasing "we emailed them" excuses when the invoice bounced three weeks ago and everyone can see it. - On
spamreport: flag the contact and suppress future automated sends. Continuing to email someone who reported you as spam is how domains get blocklisted.
Because I stamped acuRefNbr and acuDocType into custom_args, matching an event back to the Acumatica document is a dictionary lookup, not a fuzzy search on subject lines. Verify the webhook signature (SendGrid signs with an ECDSA key you fetch from Mail Settings) and treat events as at-least-once — duplicates happen, so the write into Acumatica must be idempotent. Writing the same "bounced" status twice is harmless; creating two activities is annoying; sending two internal alert emails is how people learn to ignore alerts.
Field notes and gotchas
- Suppression lists are global per SendGrid account. A hard bounce recorded while testing with a real customer address suppresses that address for production sends too. Check the suppressions dashboard before debugging "SendGrid ate my email".
- Acumatica's SMTP timeout is patient; queues are not. If SendGrid throttles you (free/low tiers have daily caps), Acumatica's system email queue just piles up quietly. Put the Email Pending Processing screen (SM507000) on your monitoring checklist, or watch it via a GI-based business event.
- Regional deliverability is real. Sending to Kenyan corporates I see far more aggressive greylisting than tutorials assume — first delivery attempts deferred by minutes. Delivery events, not send success, are the truth.
- Keep internal mail on the relay. There's no reason to route approval notifications through dynamic templates. SMTP relay for internal, API for customer-facing is a clean split.
Wrapping up
Start with the SMTP relay — it's configuration only, and it immediately buys you authentication, suppression handling, and delivery stats for everything Acumatica already sends. Graduate the customer-facing documents to the v3 API with dynamic templates when design and tracking start to matter, and wire the Event Webhook back into Acumatica so bounces become visible facts on the customer record instead of silent failures. The send is the easy 20%; the feedback loop is the part the business will actually thank you for.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.