[FIXED] Email Receive Hook - Call to Uplink Server Function Fails

Hey there,

I’m having some issues with a specific e-Mail related thing I’m trying to do and I’m not sure whether this is by design or not, so I thought I’d ask here in case anybody has come across this issue as well.

My intention is to do the following:

  • The user sends an e-Mail to the app via the Anvil e-Mail service.
  • A receive hook receives the e-Mail on the Anvil server. This part works. I can receive it just fine in my hook.
  • After doing some validation, I then try to dispatch a server function which is registered on an Uplink server to process some stuff I extracted from the e-Mail.

The last step always fails with a NoServerFunctionError.
As an idiot-test I called the exact same Uplink server function with a button from the client interface, and that worked just fine.
I also tried dispatching a background task and calling my Uplink function in there, but no dice, same error.

I’m not sure why this is the case, my current theory is that there’s some issue with the call context, specifically when the call originates in the e-Mail handler.

In code, the basic setup looks like this:

@email.handle_message()
def message_handler(msg):
    # Do some local stuff on the e-Mail
    anvil.server.call("my_uplink_function", some_input)  # <-- NoServerFunctionError

On the Uplink side, I then simply have:

# anvil connect call

@server.callable
def my_uplink_function(some_input):
    # Process the input
    return "some_output"

# anvil wait_forever call

I’m hoping somebody can shed some light on this or possibly provide an alternative way of getting data to my Uplink server to process things. Unfortunately HTTP requests are difficult due to my network situation, so an Uplink-centric way of doing things would be preferred.

1 Like

It could be very simple. I believe it’s spelled @anvil.server.callable.

1 Like

Wow that’s a great catch. I looked at the code, it was very well written and described, and I couldn’t see any problems with it!

That is simply an artifact of me importing it as follows:

from anvil import server

Which should be functionally equivalent to just referencing it directly.

1 Like

One might hope… Does it work if you use the Anvil-documented syntax verbatim? I never tried that particular shortcut. (If it ain’t broke…)

I’ve tried combinations of things with different import paths, but alas, no dice.
In this case I would consider that a good thing, since that means import behavior is consistent.

Edit: I ran some more tests focusing on the call context, but to no avail. Apparently the context for the mail handler is server_module, which is trusted, as it should be. Thus it should theoretically have no problems calling uplink code.

I was able to move all kinds of parameters and Media objects between Uplink and Anvil, but every time the e-Mail handler hits, it’s game over.

I’ve been spending some time trying to find a solution based on what Meredydd said here: by having the very same server code and function definition both in ServerModule2 and in the uplink, I was hoping that at runtime the uplink would be preferred over Anvil hosted server environment, but that proved false.
Clone this app.
Take ServerModule2’s code and copy-paste it into an uplinked script. Execute it and let it connect.
Send input from webform: prints are printed on uplinked’s output.
Send an email: prints are printed in App’s log.
This app doesn’t goes in error, but you can see from the prints in the App’s log (and the missing prints in uplinked script’s output) that the server function is executed on Anvil’s server when invoked by the email manager.
I share a copyapp for the convenience of the others who might want to help without the hassle of building a test-case from scratch.
I have no more ideas, sorry @timwedde.

https://anvil.works/build#clone:DZTD5PZ4ZFH5E2Q4=MTTEWPDYNJOPNUBJGL45APXR

Thanks for taking the time to look further into this, it’s appreciated.

The behavior in this case does seem really weird, I can’t seem to find a reason as to why this wouldn’t work.

I also took a look at the open-sourced components, i.e. the Anvil runtime, but wasn’t able to find anything in there that would indicate why it behaves the way it does.

Maybe @meredydd himself can shed some light on this.

Something else I’ve noticed as I’m testing more things, which may or may not be related to the original issue: Silently failing e-Mail delivery issues.

I have a custom domain registered for Anvil, which also gets added as a valid sender and receiver domain for the mail service.

What’s strange is that I can receive e-Mail on all the possible domains (so the Anvil-provided ones as well as my custom one), but sending a reply is not possible for my custom domain.

This can be produced with a setup as simple as this in a server module:

@anvil.email.handle_message()
def message_handler(msg):
    msg.reply(text="Test Reply")

If I send an e-Mail to anything@custom_domain.anvil.app, the handler fires and I get a reply back.
If i send an e-Mail to anything@fully_custom_domain, the handler fires but the reply vanishes, never to be seen again. No error is generated.

This all seems rather strange to me, so I’m wondering whether there’s either a configuration issue on my end, or a more serious issue on Anvil’s.

Hi @timwedde,

This all sounds very strange!

Could you try constructing a minimal app that demonstrates this behaviour, which you don’t mind sharing with us?

Alternatively, if you can’t replicate it on its own, the first thing I’d suggest investigating is whether you can call that uplink function from elsewhere in your app (eg client code or server functions). Is it only failing in the email handler, or are you not able to call it from anywhere?

As for the email question: Are you using Anvil’s built-in email sending, or are you using a custom SMTP server? Because if you try to send from a custom domain using Anvil’s built-in email sending, there are all sorts of checks that could just cause the email to disappear (eg DKIM stuff) - generally, if you’re sending from your own domain, a custom SMTP server is often a good idea.

Thanks for responding, @meredydd !

As far as a minimal working example goes, @aldo.ercolani has thankfully already provided one slightly further up in the thread, which also exposes this behavior: Email Receive Hook - Call to Uplink Server Function Fails

Regarding the e-Mail reply issue, I am indeed currently using Anvil’s server with a custom domain. I have a custom SMTP server available, so I’ll just switch to using that, if it works more reliably.

Edit: I’ve switched to my own SMTP server and e-Mails from my custom domain are now being delivered successfully, so that part is resolved (and seems to not have been related to the original e-Mail context issue).

Any updates on this @meredydd?
Were you able to replicate this with the example app provided further up in this thread?

Just to make positively sure that I’m not going insane, I re-tested everything both on my main app that I’m working on as well as a clean one, and both do still have this issue.

Just tested this again and the bug is still there.
NoServerFunctionError when calling uplink functions from the mail handler.

Is there anywhere I can file an actual bug report, @meredydd?

@aldo.ercolani’s app call isn’t going to the Uplink because you’re importing and calling the my_uplink_function() function directly from message_handler(), which means it doesn’t go through the anvil.server.call() routing, so it can never end up on the uplink.

However, it also appears that there is a call routing issue going on here, because when I changed it to use anvil.server.call() the problem remained! I’ve moved this thread into Bug Reports for futher investigation…

When I last looked at their example, the test did both, specifically to show that one of them worked while the anvil.server call did not:

@anvil.email.handle_message
def message_handler(msg):
    print('1) message handler:{text}'.format(text=msg.text))
    output = my_uplink_function(some_input=msg.text) 
    print('1) message handler returns:{output}'.format(output=output))
    print('2) subject handler:{text}'.format(text=msg.text))
    output = anvil.server.call('my_uplink_function',some_input=msg.subject) 
    print('2) subject handler returns:{output}'.format(output=output))
    msg.reply(text="test reply")

While it does indeed import the function for calling it directly at the top of the module, it also calls it via the proper mechanism, to show the difference. As you’ve already seen, the problem does actually occur as described.

1 Like

Any updates on this?
I just tested this again, seems like the issue still exists.

Hi @timwedde,

We have now fixed the issue. anvil.server.call()s should now be correctly routed to the Uplink. Please let me know if you are still seeing any issues.

1 Like

Just tested it and I can verify that this now works as expected. Thank you for the great work!

2 Likes