SendGrid powers email delivery in AppEngine — both transactional (order confirmations, password resets) and marketing broadcasts. The integration uses SendGrid's API key auth and posts events back via the Connect webhook handler.
Connect SendGrid
await fetch('/api/upstream/save-integration', {
method: 'POST',
headers: { orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
body: JSON.stringify({
type: 'sendgrid-provider',
name: 'SendGrid Production',
credentials: { apiKey: 'SG.xxx' },
publicConfig: {
defaultFromEmail: '[email protected]',
defaultFromName: 'Example Co',
replyToEmail: '[email protected]',
},
}),
});
Required scopes on the API key: Mail Send, Tracking, Suppressions, Marketing (if running campaigns).
Domain authentication
Before sending production email, authenticate your domain in SendGrid (SPF + DKIM). The platform exposes the broadcast email-domain configurator that walks through DNS record creation:
/broadcast/email-domain/configureJWTWithout authentication, SendGrid sends mail but it's marked via sendgrid.net in clients, which hurts deliverability.
Sending email
await fetch('/api/upstream/call/sendgrid-provider/send', {
method: 'POST',
headers: { orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
body: JSON.stringify({
to: '[email protected]',
subject: 'Your order has shipped',
html: '<p>Tracking: <a href="...">123</a></p>',
text: 'Tracking: 123',
from: '[email protected]',
customArgs: { orderId: 'ord-12345' },
}),
});
customArgs propagates through delivery, open, and click events — used to attribute engagement back to the originating record.
For broadcast campaigns, the Broadcast module handles segment expansion and rate limiting; you don't call send per recipient.
Webhooks
/connect/webhook/sendgridNo authSendGrid posts an array of events: processed, delivered, open, click, bounce, dropped, deferred, spam_report, unsubscribe, group_unsubscribe. The Connect module verifies the signature using the SendGrid public key (configured in your SendGrid Mail Settings → Event Webhook), then dispatches:
delivered→ mark message delivered on the broadcast recordopen→ writeemail_openedactivity on the recipient contactclick→ writeemail_clickedactivitybounce→ mark recipient bounced; future sends suppressunsubscribe→ mark recipient unsubscribed
Wire the webhook URL in SendGrid: https://api.appmint.io/connect/webhook/sendgrid?orgid=<your-org>.
Suppression lists
SendGrid maintains its own bounce, spam, and unsubscribe lists per API key. The platform reads them via:
await fetch('/api/upstream/call/sendgrid-provider/suppressions', {
method: 'GET',
headers: { orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
});
Suppressed addresses are skipped at broadcast send time, regardless of audience targeting. To remove a suppression (after the customer asks to be re-subscribed):
await fetch('/api/upstream/call/sendgrid-provider/remove-suppression', {
method: 'POST',
headers: { orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
body: JSON.stringify({ email: '[email protected]', group: 'bounces' }),
});
Inbound parse
SendGrid's Inbound Parse can route emails to the platform — useful for support inboxes that accept replies as ticket comments. Configure the parse URL to:
https://api.appmint.io/connect/webhook/sendgrid/inbound?orgid=<your-org>
The platform parses the multipart body, extracts the original message ID from the In-Reply-To header, and posts it as a reply on the matching ticket.
Tracking pixels and click redirects
SendGrid handles open tracking and click rewriting natively. AppEngine adds an additional layer: the unified automation webhook at /connect/automation/:id/:stepId/:entity/:activity/ stamps engagement events into the CRM activity log. The two layers don't conflict — SendGrid's events feed deliverability, AppEngine's track per-record engagement for automation triggers.
Common quirks
- Single Sender vs Domain Auth — Single Sender works for low-volume but is rate-limited and lower-deliverability. Always use domain auth in production.
- Sandbox mode — append
mail_settings.sandbox_mode.enable: trueto a send to validate without sending. The platform doesn't expose this via Upstream; if you need it, call SendGrid directly with the same key. - Categories vs custom args — the connector uses
customArgsconsistently for record IDs. Don't put record IDs in SendGridcategories— they're rate-limited differently.
For redundancy, configure both SendGrid and Mailgun. The use-case routing layer can fall back to the secondary vendor if the primary is rate-limited or down.